ETH Price: $1,840.63 (+1.47%)

Contract

0x5acAf61d339dd123e60ba450Ea38fbC49445007C
 

Overview

ETH Balance

95.502600000317985624 ETH

Eth Value

$175,784.76 (@ $1,840.63/ETH)

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To

There are no matching entries

> 10 Internal Transactions found.

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Method Block
From
To
Deposit Ether221671772025-03-31 13:24:115 hrs ago1743427451
0x5acAf61d...49445007C
0.5 ETH
Deposit Ether221650182025-03-31 6:10:5912 hrs ago1743401459
0x5acAf61d...49445007C
0.001 ETH
Deposit Ether221474492025-03-28 19:18:232 days ago1743189503
0x5acAf61d...49445007C
0.001 ETH
Deposit Ether221456182025-03-28 13:09:593 days ago1743167399
0x5acAf61d...49445007C
0.0005 ETH
Deposit Ether221408692025-03-27 21:14:593 days ago1743110099
0x5acAf61d...49445007C
0.0001 ETH
Deposit Ether221403162025-03-27 19:23:233 days ago1743103403
0x5acAf61d...49445007C
20 ETH
Transfer221331482025-03-26 19:22:594 days ago1743016979
0x5acAf61d...49445007C
276.58077817 ETH
Deposit Ether221301962025-03-26 9:31:235 days ago1742981483
0x5acAf61d...49445007C
0.0003 ETH
Deposit Ether221194862025-03-24 21:35:356 days ago1742852135
0x5acAf61d...49445007C
6 ETH
Deposit Ether221169522025-03-24 13:05:597 days ago1742821559
0x5acAf61d...49445007C
0.0003 ETH
Deposit Ether221154452025-03-24 8:03:357 days ago1742803415
0x5acAf61d...49445007C
0.0005 ETH
Deposit Ether221150772025-03-24 6:49:477 days ago1742798987
0x5acAf61d...49445007C
1.5 ETH
Deposit Ether221123232025-03-23 21:37:357 days ago1742765855
0x5acAf61d...49445007C
0.5 ETH
Deposit Ether221123122025-03-23 21:35:237 days ago1742765723
0x5acAf61d...49445007C
0.05 ETH
Deposit Ether221122442025-03-23 21:21:477 days ago1742764907
0x5acAf61d...49445007C
0.02 ETH
Deposit Ether221116932025-03-23 19:30:597 days ago1742758259
0x5acAf61d...49445007C
0.0001 ETH
Deposit Ether221116882025-03-23 19:29:597 days ago1742758199
0x5acAf61d...49445007C
0.0001 ETH
Deposit Ether221102172025-03-23 14:33:358 days ago1742740415
0x5acAf61d...49445007C
0.00052984 ETH
Deposit Ether221096952025-03-23 12:48:478 days ago1742734127
0x5acAf61d...49445007C
0.001 ETH
Deposit Ether221057132025-03-22 23:30:118 days ago1742686211
0x5acAf61d...49445007C
0.59 ETH
Deposit Ether221036182025-03-22 16:29:239 days ago1742660963
0x5acAf61d...49445007C
10 ETH
Deposit Ether221034922025-03-22 16:04:119 days ago1742659451
0x5acAf61d...49445007C
23 ETH
Deposit Ether221034792025-03-22 16:01:359 days ago1742659295
0x5acAf61d...49445007C
60 ETH
Deposit Ether221014232025-03-22 9:07:479 days ago1742634467
0x5acAf61d...49445007C
6.85 ETH
Deposit Ether220932392025-03-21 5:46:1110 days ago1742535971
0x5acAf61d...49445007C
4 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
EtherRouter

Compiler Version
v0.8.28+commit.7893614a

Optimization Enabled:
Yes with 800 runs

Other Settings:
cancun EvmVersion
File 1 of 44 : EtherRouter.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// =========================== EtherRouter ============================
// ====================================================================
// Manages ETH and ETH-like tokens (frxETH, rETH, stETH, etc) in different AMOs and moves them between there
// and the Lending Pool
// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { RedemptionQueueV2Role } from "../access-control/RedemptionQueueV2Role.sol";
import { LendingPoolRole, LendingPool } from "../access-control/LendingPoolRole.sol";
import { OperatorRole } from "frax-std/access-control/v2/OperatorRole.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IfrxEthV2AMO } from "./interfaces/IfrxEthV2AMO.sol";
import { IfrxEthV2AMOHelper } from "./interfaces/IfrxEthV2AMOHelper.sol";
import { PublicReentrancyGuard } from "frax-std/access-control/v2/PublicReentrancyGuard.sol";

/// @title Recieves and gives back ETH from the lending pool. Distributes idle ETH to various AMOs for use, such as LP formation.
/// @author Frax Finance
/// @notice Controlled by Frax governance
contract EtherRouter is LendingPoolRole, RedemptionQueueV2Role, OperatorRole, Timelock2Step, PublicReentrancyGuard {
    using SafeERC20 for ERC20;

    // ========================================================
    // STATE VARIABLES
    // ========================================================

    // AMO addresses
    /// @notice Array of AMOs
    address[] public amosArray;

    /// @notice Mapping is also used for faster verification
    mapping(address => bool) public amos; //

    /// @notice For caching getConsolidatedEthFrxEthBalance
    // mapping(address => bool) public staleStatusCEFEBals;
    mapping(address => CachedConsEFxBalances) public cachedConsEFxEBals;

    /// @notice Address where all ETH deposits will go to
    address public depositToAmoAddr;

    /// @notice Address where requestEther will pull from first
    address public primaryWithdrawFromAmoAddr;

    /// @notice Address of frxETH
    ERC20 public immutable frxETH;

    // ========================================================
    // STRUCTS
    // ========================================================

    /// @notice Get frxETH/sfrxETH and ETH/LSD/WETH balances
    /// @param isStale If the cache is stale or not
    /// @param amoAddress Address of the AMO for this cache
    /// @param ethFree Free and clear ETH/LSD/WETH
    /// @param ethInLpBalanced ETH/LSD/WETH in LP (balanced withdrawal)
    /// @param ethTotalBalanced Free and clear ETH/LSD/WETH + ETH/LSD/WETH in LP (balanced withdrawal)
    /// @param frxEthFree Free and clear frxETH/sfrxETH
    /// @param frxEthInLpBalanced frxETH/sfrxETH in LP (balanced withdrawal)
    struct CachedConsEFxBalances {
        bool isStale;
        address amoAddress;
        uint96 ethFree;
        uint96 ethInLpBalanced;
        uint96 ethTotalBalanced;
        uint96 frxEthFree;
        uint96 frxEthInLpBalanced;
    }

    // ========================================================
    // CONSTRUCTOR
    // ========================================================

    /// @notice Constructor for the EtherRouter
    /// @param _timelockAddress The timelock address
    /// @param _operatorAddress The operator address
    /// @param _frxEthAddress The address of the frxETH ERC20
    constructor(
        address _timelockAddress,
        address _operatorAddress,
        address _frxEthAddress
    )
        RedemptionQueueV2Role(payable(address(0)))
        LendingPoolRole(payable(address(0)))
        OperatorRole(_operatorAddress)
        Timelock2Step(_timelockAddress)
    {
        frxETH = ERC20(_frxEthAddress);
    }

    // ====================================
    // INTERNAL FUNCTIONS
    // ====================================

    /// @notice Checks if msg.sender is current timelock address or the operator
    function _requireIsTimelockOrOperator() internal view {
        if (!((msg.sender == timelockAddress) || (msg.sender == operatorAddress))) revert NotTimelockOrOperator();
    }

    /// @notice Checks if msg.sender is the lending pool or the redemption queue
    function _requireSenderIsLendingPoolOrRedemptionQueue() internal view {
        if (!((msg.sender == address(lendingPool)) || (msg.sender == address(redemptionQueue)))) {
            revert NotLendingPoolOrRedemptionQueue();
        }
    }

    // ========================================================
    // VIEWS
    // ========================================================

    /// @notice Get frxETH/sfrxETH and ETH/LSD/WETH balances
    /// @param _forceLive Force a live recalculation of the AMO values
    /// @param _previewUpdateCache Calculate, but do not write, updated cache values
    /// @return _rtnTtlBalances frxETH/sfrxETH and ETH/LSD/WETH balances
    /// @return _cachesToUpdate Caches to be updated, if specified in _previewUpdateCache
    function _getConsolidatedEthFrxEthBalanceViewCore(
        bool _forceLive,
        bool _previewUpdateCache
    )
        internal
        view
        returns (CachedConsEFxBalances memory _rtnTtlBalances, CachedConsEFxBalances[] memory _cachesToUpdate)
    {
        // Initialize _cachesToUpdate
        CachedConsEFxBalances[] memory _cachesToUpdateLocal = new CachedConsEFxBalances[](amosArray.length);

        // Add ETH sitting in this contract first
        // frxETH/sfrxETH should never be here
        // _rtnTtlBalances.isStale = false
        _rtnTtlBalances.ethFree += uint96(address(this).balance);
        _rtnTtlBalances.ethTotalBalanced += uint96(address(this).balance);

        // Loop through all the AMOs and sum
        for (uint256 i = 0; i < amosArray.length; ) {
            address _amoAddress = amosArray[i];
            // Skip removed AMOs
            if (_amoAddress != address(0)) {
                // Pull the cache entry
                CachedConsEFxBalances memory _cacheEntry = cachedConsEFxEBals[_amoAddress];

                // If the caller wants to force a live calc, or the cache is stale
                if (_cacheEntry.isStale || _forceLive) {
                    IfrxEthV2AMOHelper.ShowAmoBalancedAllocsPacked memory _packedBals = IfrxEthV2AMOHelper(
                        IfrxEthV2AMO(_amoAddress).amoHelper()
                    ).getConsolidatedEthFrxEthBalancePacked(_amoAddress);

                    // Add to the return totals
                    _rtnTtlBalances.ethFree += _packedBals.amoEthFree;
                    _rtnTtlBalances.ethInLpBalanced += _packedBals.amoEthInLpBalanced;
                    _rtnTtlBalances.ethTotalBalanced += _packedBals.amoEthTotalBalanced;
                    _rtnTtlBalances.frxEthFree += _packedBals.amoFrxEthFree;
                    _rtnTtlBalances.frxEthInLpBalanced += _packedBals.amoFrxEthInLpBalanced;

                    // If the cache should be updated (per the input params)
                    if (_previewUpdateCache) {
                        // Push to the return array
                        // Would have rather wrote to storage here, but the compiler complained about the view "mutability"
                        _cachesToUpdateLocal[i] = CachedConsEFxBalances(
                            false,
                            _amoAddress,
                            _packedBals.amoEthFree,
                            _packedBals.amoEthInLpBalanced,
                            _packedBals.amoEthTotalBalanced,
                            _packedBals.amoFrxEthFree,
                            _packedBals.amoFrxEthInLpBalanced
                        );
                    }
                } else {
                    // Otherwise, just read from the cache
                    _rtnTtlBalances.ethFree += _cacheEntry.ethFree;
                    _rtnTtlBalances.ethInLpBalanced += _cacheEntry.ethInLpBalanced;
                    _rtnTtlBalances.ethTotalBalanced += _cacheEntry.ethTotalBalanced;
                    _rtnTtlBalances.frxEthFree += _cacheEntry.frxEthFree;
                    _rtnTtlBalances.frxEthInLpBalanced += _cacheEntry.frxEthInLpBalanced;
                }
            }
            unchecked {
                ++i;
            }
        }

        // Update the return value
        _cachesToUpdate = _cachesToUpdateLocal;
    }

    /// @notice Get frxETH/sfrxETH and ETH/LSD/WETH balances
    /// @param _forceLive Force a live recalculation of the AMO values
    /// @param _updateCache Whether to update the cache
    /// @return _rtnBalances frxETH/sfrxETH and ETH/LSD/WETH balances
    function getConsolidatedEthFrxEthBalance(
        bool _forceLive,
        bool _updateCache
    ) external returns (CachedConsEFxBalances memory _rtnBalances) {
        CachedConsEFxBalances[] memory _cachesToUpdate;
        // Determine the route
        if (_updateCache) {
            // Fetch the return balances as well as the new balances to cache
            (_rtnBalances, _cachesToUpdate) = _getConsolidatedEthFrxEthBalanceViewCore(_forceLive, true);

            // Loop through the caches and store them
            for (uint256 i = 0; i < _cachesToUpdate.length; ) {
                // Get the address of the AMO
                address _amoAddress = _cachesToUpdate[i].amoAddress;

                // Skip caches that don't need to be updated
                if (_amoAddress != address(0)) {
                    // Update storage
                    cachedConsEFxEBals[_amoAddress] = _cachesToUpdate[i];
                }
                unchecked {
                    ++i;
                }
            }
        } else {
            // Don't care about updating the cache, so return early
            (_rtnBalances, ) = _getConsolidatedEthFrxEthBalanceViewCore(_forceLive, false);
        }
    }

    /// @notice Get frxETH/sfrxETH and ETH/LSD/WETH balances
    /// @param _forceLive Force a live recalculation of the AMO values
    /// @return _rtnBalances frxETH/sfrxETH and ETH/LSD/WETH balances
    function getConsolidatedEthFrxEthBalanceView(
        bool _forceLive
    ) external view returns (CachedConsEFxBalances memory _rtnBalances) {
        // Return the view-only component
        (_rtnBalances, ) = _getConsolidatedEthFrxEthBalanceViewCore(_forceLive, false);
    }

    // ========================================================
    // CALLED BY LENDING POOL
    // ========================================================

    /// @notice Lending Pool or Minter or otherwise -> ETH -> This Ether Router
    function depositEther() external payable {
        // Do nothing for now except accepting the ETH
    }

    /// @notice Use a private transaction. Router will deposit ETH first into the redemption queue, if there is a shortage. Any leftover ETH goes to the default depositToAmoAddr.
    /// @param _amount Amount to sweep. Will use contract balance if = 0
    /// @param _depositAndVault Whether you want to just dump the ETH in the Curve AMO, or if you want to wrap and vault it too
    function sweepEther(uint256 _amount, bool _depositAndVault) external {
        _requireIsTimelockOrOperator();

        // Add interest first
        lendingPool.addInterest(false);

        // Use the entire contract balance if _amount is 0
        if (_amount == 0) _amount = address(this).balance;

        // See if the redemption queue has a shortage
        (, uint256 _rqShortage) = redemptionQueue.ethShortageOrSurplus();

        // Take care of any shortage first
        if (_amount <= _rqShortage) {
            // Give all you can to help address the shortage
            (bool sent, ) = payable(redemptionQueue).call{ value: _amount }("");
            if (!sent) revert EthTransferFailedER(0);

            emit EtherSwept(address(redemptionQueue), _amount);
        } else {
            // First fulfill the shortage, if any
            if (_rqShortage > 0) {
                (bool sent, ) = payable(redemptionQueue).call{ value: _rqShortage }("");
                if (!sent) revert EthTransferFailedER(1);

                emit EtherSwept(address(redemptionQueue), _rqShortage);
            }

            // Calculate the remaining ETH
            uint256 _remainingEth = _amount - _rqShortage;

            // Make sure the AMO is not the zero address, then deposit to it
            if (depositToAmoAddr != address(0)) {
                // Send ETH to the AMO. Either 1) Leave it alone, or 2) Deposit it into cvxLP + vault it
                if (_depositAndVault) {
                    // Drop in, deposit, and vault
                    IfrxEthV2AMO(depositToAmoAddr).depositEther{ value: _remainingEth }();
                } else {
                    // Drop in only
                    (bool sent, ) = payable(depositToAmoAddr).call{ value: _remainingEth }("");
                    if (!sent) revert EthTransferFailedER(2);
                }

                // Mark the getConsolidatedEthFrxEthBalance cache as stale for this AMO
                cachedConsEFxEBals[depositToAmoAddr].isStale = true;
            }

            emit EtherSwept(depositToAmoAddr, _remainingEth);
        }

        // Update the stored utilization rate
        lendingPool.updateUtilization();
    }

    /// @notice See how ETH would flow if requestEther were called
    /// @param _ethRequested Amount of ETH requested
    /// @return _currEthInRouter How much ETH is currently in this contract
    /// @return _rqShortage How much the ETH shortage in the redemption queue is, if any
    /// @return _pullFromAmosAmount How much ETH would need to be pulled from various AMO(s)
    function previewRequestEther(
        uint256 _ethRequested
    ) public view returns (uint256 _currEthInRouter, uint256 _rqShortage, uint256 _pullFromAmosAmount) {
        // See how much ETH is already in this contract
        _currEthInRouter = address(this).balance;

        // See if the redemption queue has a shortage
        (, _rqShortage) = redemptionQueue.ethShortageOrSurplus();

        // Determine where to get the ETH from
        if ((_ethRequested + _rqShortage) <= _currEthInRouter) {
            // Do nothing, the ETH will be pulled from existing funds in this contract
        } else {
            // Calculate the extra amount needed from various AMO(s)
            _pullFromAmosAmount = _ethRequested + _rqShortage - _currEthInRouter;
        }
    }

    /// @notice AMO(s) -> ETH -> (Lending Pool or Redemption Queue). Instruct the router to get ETH from various AMO(s) (free and vaulted)
    /// @param _recipient Recipient of the ETH
    /// @param _ethRequested Amount of ETH requested
    /// @param _bypassFullRqShortage If someone wants to redeem and _rqShortage is too large, send back what you can
    /// @dev Need to pay off any shortage in the redemption queue first
    function requestEther(
        address payable _recipient,
        uint256 _ethRequested,
        bool _bypassFullRqShortage
    ) external nonReentrant {
        // Only the LendingPool or RedemptionQueue can call
        _requireSenderIsLendingPoolOrRedemptionQueue();

        // Add interest
        lendingPool.addInterestPrivileged(false);
        // if (msg.sender == address(redemptionQueue)) {
        //     lendingPool.addInterestPrivileged(false);
        // }
        // else if (msg.sender == address(lendingPool)) {
        //     lendingPool.addInterest(false);

        // }
        // else {
        //     revert NotLendingPoolOrRedemptionQueue();
        // }

        // See where the ETH is and where it needs to go
        (uint256 _currEthInRouter, uint256 _rqShortage, uint256 _pullFromAmosAmount) = previewRequestEther(
            _ethRequested
        );

        // Pull the extra amount needed from the AMO(s) first, if necessary
        uint256 _remainingEthToPull = _pullFromAmosAmount;

        // If _bypassFullRqShortage is true, we don't care about the full RQ shortage
        if (_bypassFullRqShortage) {
            if (_ethRequested <= _currEthInRouter) {
                // The ETH will be pulled from existing funds in this contract
                _remainingEthToPull = 0;
            } else {
                // Calculate the extra amount needed from various AMO(s)
                _remainingEthToPull = _ethRequested - _currEthInRouter;
            }
        }

        // Start pulling from the AMOs, with primaryWithdrawFromAmoAddr being preferred
        if (_remainingEthToPull > 0) {
            // Order the amos
            address[] memory _sortedAmos = new address[](amosArray.length);

            // Handle primaryWithdrawFromAmoAddr
            if (primaryWithdrawFromAmoAddr != address(0)) {
                // primaryWithdrawFromAmoAddr should be first
                _sortedAmos[0] = primaryWithdrawFromAmoAddr;

                // Loop through all the AMOs and fill _sortedAmos
                uint256 _nextIdx = 1; // [0] is always primaryWithdrawFromAmoAddr
                for (uint256 i = 0; i < amosArray.length; ++i) {
                    // Don't double add primaryWithdrawFromAmoAddr
                    if (amosArray[i] == primaryWithdrawFromAmoAddr) continue;

                    // Push the remaining AMOs in
                    _sortedAmos[_nextIdx] = amosArray[i];

                    // Increment the next index to insert at
                    ++_nextIdx;
                }
            } else {
                _sortedAmos = amosArray;
            }

            // Loop through the AMOs and pull out ETH
            for (uint256 i = 0; i < _sortedAmos.length; ) {
                if (_sortedAmos[i] != address(0)) {
                    // Pull Ether from an AMO. May return a 0, partial, or full amount
                    (uint256 _ethOut, ) = IfrxEthV2AMO(_sortedAmos[i]).requestEtherByRouter(_remainingEthToPull);

                    // Account for the collected Ether
                    _remainingEthToPull -= _ethOut;

                    // If ETH was removed, mark the getConsolidatedEthFrxEthBalance cache as stale for this AMO
                    if (_ethOut > 0) cachedConsEFxEBals[_sortedAmos[i]].isStale = true;

                    // Stop looping if it collected enough
                    if (_remainingEthToPull == 0) break;
                    unchecked {
                        ++i;
                    }
                }
            }
        }

        // Fail early if you didn't manage to collect enough, but see if it is a dust amount first
        if (_remainingEthToPull > 0) revert NotEnoughEthPulled(_remainingEthToPull);

        // Give the shortage ETH to the redemption queue, if necessary and not bypassed
        if (!_bypassFullRqShortage && (_rqShortage > 0)) {
            (bool sent, ) = payable(redemptionQueue).call{ value: _rqShortage }("");
            if (!sent) revert EthTransferFailedER(2);
        }

        // Give remaining ETH to the recipient (could be the redemption queue)
        (bool sent, ) = payable(_recipient).call{ value: _ethRequested }("");
        if (!sent) revert EthTransferFailedER(3);

        // Update the stored utilization rate
        lendingPool.updateUtilization();

        emit EtherRequested(payable(_recipient), _ethRequested, _rqShortage);
    }

    /// @notice Needs to be here to receive ETH
    receive() external payable {
        // Do nothing for now.
    }

    // ========================================================
    // RESTRICTED GOVERNANCE FUNCTIONS
    // ========================================================

    // Adds an AMO
    /// @param _amoAddress Address of the AMO to add
    function addAmo(address _amoAddress) external {
        _requireSenderIsTimelock();
        if (_amoAddress == address(0)) revert ZeroAddress();

        // Need to make sure at least that getConsolidatedEthFrxEthBalance is present
        // This will revert if it isn't there
        IfrxEthV2AMOHelper(IfrxEthV2AMO(_amoAddress).amoHelper()).getConsolidatedEthFrxEthBalance(_amoAddress);

        // Make sure the AMO isn't already here
        if (amos[_amoAddress]) revert AmoAlreadyExists();

        // Update state
        amos[_amoAddress] = true;
        amosArray.push(_amoAddress);

        emit FrxEthAmoAdded(_amoAddress);
    }

    // Removes an AMO
    /// @param _amoAddress Address of the AMO to remove
    function removeAmo(address _amoAddress) external {
        _requireSenderIsTimelock();
        if (_amoAddress == address(0)) revert ZeroAddress();
        if (!amos[_amoAddress]) revert AmoAlreadyOffOrMissing();

        // Delete from the mapping
        delete amos[_amoAddress];

        // 'Delete' from the array by setting the address to 0x0
        for (uint256 i = 0; i < amosArray.length; ) {
            if (amosArray[i] == _amoAddress) {
                amosArray[i] = address(0); // This will leave a null in the array and keep the indices the same
                break;
            }
            unchecked {
                ++i;
            }
        }

        emit FrxEthAmoRemoved(_amoAddress);
    }

    /// @notice Set preferred AMO addresses to deposit to / withdraw from
    /// @param _depositToAddress New address for the ETH deposit destination
    /// @param _withdrawFromAddress New address for the primary ETH withdrawal source
    function setPreferredDepositAndWithdrawalAMOs(address _depositToAddress, address _withdrawFromAddress) external {
        _requireIsTimelockOrOperator();

        // Make sure they are actually AMOs
        if (!amos[_depositToAddress] || !amos[_withdrawFromAddress]) revert InvalidAmo();

        // Set the addresses
        depositToAmoAddr = _depositToAddress;
        primaryWithdrawFromAmoAddr = _withdrawFromAddress;

        emit PreferredDepositAndWithdrawalAmoAddressesSet(_depositToAddress, _withdrawFromAddress);
    }

    /// @notice Sets the lending pool, where ETH is taken from / given to
    /// @param _newAddress New address for the lending pool
    function setLendingPool(address _newAddress) external {
        _requireSenderIsTimelock();
        _setLendingPool(payable(_newAddress));
    }

    /// @notice Change the Operator address
    /// @param _newOperatorAddress Operator address
    function setOperatorAddress(address _newOperatorAddress) external {
        _requireSenderIsTimelock();
        _setOperator(_newOperatorAddress);
    }

    /// @notice Sets the redemption queue, where frxETH is redeemed for ETH. Only callable once
    /// @param _newAddress New address for the redemption queue
    function setRedemptionQueue(address _newAddress) external {
        _requireSenderIsTimelock();

        // Only can set once
        if (payable(redemptionQueue) != payable(0)) revert RedemptionQueueAddressAlreadySet();

        _setFraxEtherRedemptionQueueV2(payable(_newAddress));
    }

    // ==============================================================================
    // Recovery Functions
    // ==============================================================================

    /// @notice For taking lending interest profits, or removing excess ETH. Proceeds go to timelock.
    /// @param _amount Amount of ETH to recover
    function recoverEther(uint256 _amount) external {
        _requireSenderIsTimelock();

        (bool _success, ) = address(timelockAddress).call{ value: _amount }("");
        if (!_success) revert InvalidRecoverEtherTransfer();

        emit EtherRecovered(_amount);
    }

    /// @notice For emergencies if someone accidentally sent some ERC20 tokens here. Proceeds go to timelock.
    /// @param _tokenAddress Address of the ERC20 to recover
    /// @param _tokenAmount Amount of the ERC20 to recover
    function recoverErc20(address _tokenAddress, uint256 _tokenAmount) external {
        _requireSenderIsTimelock();

        ERC20(_tokenAddress).safeTransfer({ to: timelockAddress, value: _tokenAmount });

        emit Erc20Recovered(_tokenAddress, _tokenAmount);
    }

    // ========================================================
    // ERRORS
    // ========================================================

    /// @notice When you are trying to add an AMO that already exists
    error AmoAlreadyExists();

    /// @notice When you are trying to remove an AMO that is already removed or doesn't exist
    error AmoAlreadyOffOrMissing();

    /// @notice When an Ether transfer fails
    /// @param step A marker in the code where it is failing
    error EthTransferFailedER(uint256 step);

    /// @notice When you are trying to interact with an invalid AMO
    error InvalidAmo();

    /// @notice Invalid ETH transfer during recoverEther
    error InvalidRecoverEtherTransfer();

    /// @notice If requestEther was unable to pull enough ETH from AMOs to satify a request
    /// @param remainingEth The amount remaining that was unable to be pulled
    error NotEnoughEthPulled(uint256 remainingEth);

    /// @notice Thrown if the sender is not the lending pool or the redemption queue
    error NotLendingPoolOrRedemptionQueue();

    /// @notice Thrown if the sender is not the timelock or the operator
    error NotTimelockOrOperator();

    /// @notice Thrown if the redemption queue address was already set
    error RedemptionQueueAddressAlreadySet();

    /// @notice When an provided address is address(0)
    error ZeroAddress();

    // ========================================================
    // EVENTS
    // ========================================================

    /// @notice When recoverEther is called
    /// @param amount The amount of Ether recovered
    event EtherRecovered(uint256 amount);

    /// @notice When recoverErc20 is called
    /// @param tokenAddress The address of the ERC20 token being recovered
    /// @param tokenAmount The quantity of the token
    event Erc20Recovered(address tokenAddress, uint256 tokenAmount);

    /// @notice When Ether is requested and sent out
    /// @param requesterAddress Address of the requester
    /// @param amountToRequester Amount of ETH sent to the requester
    /// @param amountToRedemptionQueue Amount of ETH sent to the redemption queue
    event EtherRequested(address requesterAddress, uint256 amountToRequester, uint256 amountToRedemptionQueue);

    /// @notice When Ether is moved from this contract into the redemption queue or AMO(s)
    /// @param destAddress Where the ETH was swept into
    /// @param amount Amount of the swept ETH
    event EtherSwept(address destAddress, uint256 amount);

    /// @notice When an AMO is added
    /// @param amoAddress The address of the added AMO
    event FrxEthAmoAdded(address amoAddress);

    /// @notice When an AMO is removed
    /// @param amoAddress The address of the removed AMO
    event FrxEthAmoRemoved(address amoAddress);

    /// @notice When the preferred AMO addresses to deposit to / withdraw from are set
    /// @param depositToAddress Which AMO incoming ETH should be sent to
    /// @param withdrawFromAddress New address for the primary ETH withdrawal source
    event PreferredDepositAndWithdrawalAmoAddressesSet(address depositToAddress, address withdrawFromAddress);
}

File 2 of 44 : ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     * ```
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}

File 3 of 44 : Timelock2Step.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================== Timelock2Step ===========================
// ====================================================================
// Frax Finance: https://github.com/FraxFinance

// Primary Author
// Drake Evans: https://github.com/DrakeEvans

// Reviewers
// Dennis: https://github.com/denett

// ====================================================================

/// @title Timelock2Step
/// @author Drake Evans (Frax Finance) https://github.com/drakeevans
/// @dev Inspired by OpenZeppelin's Ownable2Step contract
/// @notice  An abstract contract which contains 2-step transfer and renounce logic for a timelock address
abstract contract Timelock2Step {
    /// @notice The pending timelock address
    address public pendingTimelockAddress;

    /// @notice The current timelock address
    address public timelockAddress;

    constructor(address _timelockAddress) {
        timelockAddress = _timelockAddress;
    }

    // ============================================================================================
    // Functions: External Functions
    // ============================================================================================

    /// @notice The ```transferTimelock``` function initiates the timelock transfer
    /// @dev Must be called by the current timelock
    /// @param _newTimelock The address of the nominated (pending) timelock
    function transferTimelock(address _newTimelock) external virtual {
        _requireSenderIsTimelock();
        _transferTimelock(_newTimelock);
    }

    /// @notice The ```acceptTransferTimelock``` function completes the timelock transfer
    /// @dev Must be called by the pending timelock
    function acceptTransferTimelock() external virtual {
        _requireSenderIsPendingTimelock();
        _acceptTransferTimelock();
    }

    /// @notice The ```renounceTimelock``` function renounces the timelock after setting pending timelock to current timelock
    /// @dev Pending timelock must be set to current timelock before renouncing, creating a 2-step renounce process
    function renounceTimelock() external virtual {
        _requireSenderIsTimelock();
        _requireSenderIsPendingTimelock();
        _transferTimelock(address(0));
        _setTimelock(address(0));
    }

    // ============================================================================================
    // Functions: Internal Actions
    // ============================================================================================

    /// @notice The ```_transferTimelock``` function initiates the timelock transfer
    /// @dev This function is to be implemented by a public function
    /// @param _newTimelock The address of the nominated (pending) timelock
    function _transferTimelock(address _newTimelock) internal {
        pendingTimelockAddress = _newTimelock;
        emit TimelockTransferStarted(timelockAddress, _newTimelock);
    }

    /// @notice The ```_acceptTransferTimelock``` function completes the timelock transfer
    /// @dev This function is to be implemented by a public function
    function _acceptTransferTimelock() internal {
        pendingTimelockAddress = address(0);
        _setTimelock(msg.sender);
    }

    /// @notice The ```_setTimelock``` function sets the timelock address
    /// @dev This function is to be implemented by a public function
    /// @param _newTimelock The address of the new timelock
    function _setTimelock(address _newTimelock) internal {
        emit TimelockTransferred(timelockAddress, _newTimelock);
        timelockAddress = _newTimelock;
    }

    // ============================================================================================
    // Functions: Internal Checks
    // ============================================================================================

    /// @notice The ```_isTimelock``` function checks if _address is current timelock address
    /// @param _address The address to check against the timelock
    /// @return Whether or not msg.sender is current timelock address
    function _isTimelock(address _address) internal view returns (bool) {
        return _address == timelockAddress;
    }

    /// @notice The ```_requireIsTimelock``` function reverts if _address is not current timelock address
    /// @param _address The address to check against the timelock
    function _requireIsTimelock(address _address) internal view {
        if (!_isTimelock(_address)) revert AddressIsNotTimelock(timelockAddress, _address);
    }

    /// @notice The ```_requireSenderIsTimelock``` function reverts if msg.sender is not current timelock address
    /// @dev This function is to be implemented by a public function
    function _requireSenderIsTimelock() internal view {
        _requireIsTimelock(msg.sender);
    }

    /// @notice The ```_isPendingTimelock``` function checks if the _address is pending timelock address
    /// @dev This function is to be implemented by a public function
    /// @param _address The address to check against the pending timelock
    /// @return Whether or not _address is pending timelock address
    function _isPendingTimelock(address _address) internal view returns (bool) {
        return _address == pendingTimelockAddress;
    }

    /// @notice The ```_requireIsPendingTimelock``` function reverts if the _address is not pending timelock address
    /// @dev This function is to be implemented by a public function
    /// @param _address The address to check against the pending timelock
    function _requireIsPendingTimelock(address _address) internal view {
        if (!_isPendingTimelock(_address)) revert AddressIsNotPendingTimelock(pendingTimelockAddress, _address);
    }

    /// @notice The ```_requirePendingTimelock``` function reverts if msg.sender is not pending timelock address
    /// @dev This function is to be implemented by a public function
    function _requireSenderIsPendingTimelock() internal view {
        _requireIsPendingTimelock(msg.sender);
    }

    // ============================================================================================
    // Functions: Events
    // ============================================================================================

    /// @notice The ```TimelockTransferStarted``` event is emitted when the timelock transfer is initiated
    /// @param previousTimelock The address of the previous timelock
    /// @param newTimelock The address of the new timelock
    event TimelockTransferStarted(address indexed previousTimelock, address indexed newTimelock);

    /// @notice The ```TimelockTransferred``` event is emitted when the timelock transfer is completed
    /// @param previousTimelock The address of the previous timelock
    /// @param newTimelock The address of the new timelock
    event TimelockTransferred(address indexed previousTimelock, address indexed newTimelock);

    // ============================================================================================
    // Functions: Errors
    // ============================================================================================

    /// @notice Emitted when timelock is transferred
    error AddressIsNotTimelock(address timelockAddress, address actualAddress);

    /// @notice Emitted when pending timelock is transferred
    error AddressIsNotPendingTimelock(address pendingTimelockAddress, address actualAddress);
}

File 4 of 44 : RedemptionQueueV2Role.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ======================== RedemptionQueueV2Role =======================
// ====================================================================
// Access control for the Frax Ether Redemption Queue

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett

import { FraxEtherRedemptionQueueV2 } from "../frxeth-redemption-queue-v2/FraxEtherRedemptionQueueV2.sol";

abstract contract RedemptionQueueV2Role {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    FraxEtherRedemptionQueueV2 public redemptionQueue;

    /// @notice constructor
    /// @param _redemptionQueue Address of Redemption Queue
    constructor(address payable _redemptionQueue) {
        redemptionQueue = FraxEtherRedemptionQueueV2(_redemptionQueue);
    }

    // ==============================================================================
    // Configuration Setters
    // ==============================================================================

    /// @notice Sets a new Redemption Queue
    /// @param _redemptionQueue Address for the new Redemption Queue.
    function _setFraxEtherRedemptionQueueV2(address payable _redemptionQueue) internal {
        emit SetFraxEtherRedemptionQueueV2(address(redemptionQueue), _redemptionQueue);
        redemptionQueue = FraxEtherRedemptionQueueV2(_redemptionQueue);
    }

    // ==============================================================================
    // Internal Checks
    // ==============================================================================

    /// @notice Checks if an address is the Redemption Queue
    /// @param _address Address to test
    function _isFraxEtherRedemptionQueueV2(address _address) internal view returns (bool) {
        return (_address == address(redemptionQueue));
    }

    /// @notice Reverts if the address is not the Redemption Queue
    /// @param _address Address to test
    function _requireIsFraxEtherRedemptionQueueV2(address _address) internal view {
        if (!_isFraxEtherRedemptionQueueV2(_address)) {
            revert AddressIsNotFraxEtherRedemptionQueueV2(address(redemptionQueue), _address);
        }
    }

    /// @notice Reverts if msg.sender is not the Redemption Queue
    function _requireSenderIsFraxEtherRedemptionQueueV2() internal view {
        _requireIsFraxEtherRedemptionQueueV2(msg.sender);
    }

    // ==============================================================================
    // Events
    // ==============================================================================

    /// @notice The ```SetFraxEtherRedemptionQueueV2``` event fires when the Redemption Queue address changes
    /// @param oldFraxEtherRedemptionQueueV2 The old address
    /// @param newFraxEtherRedemptionQueueV2 The new address
    event SetFraxEtherRedemptionQueueV2(
        address indexed oldFraxEtherRedemptionQueueV2,
        address indexed newFraxEtherRedemptionQueueV2
    );

    // ==============================================================================
    // Errors
    // ==============================================================================

    /// @notice Emitted when the test address is not the Redemption Queue
    error AddressIsNotFraxEtherRedemptionQueueV2(address redemptionQueueAddress, address actualAddress);
}

File 5 of 44 : LendingPoolRole.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================== LendingPoolRole =========================
// ====================================================================
// Access control for the Lending Pool

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans

// Reviewer(s) / Contributor(s)
// Travis Moore: https://github.com/FortisFortuna
// Dennis: https://github.com/denett

import { LendingPool } from "../lending-pool/LendingPool.sol";

abstract contract LendingPoolRole {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    LendingPool public lendingPool;

    /// @notice constructor
    /// @param _lendingPool Address of Lending Pool
    constructor(address payable _lendingPool) {
        lendingPool = LendingPool(_lendingPool);
    }

    // ==============================================================================
    // Configuration Setters
    // ==============================================================================

    /// @notice Sets a new Lending Pool
    /// @param _lendingPool Address for the new Lending Pool.
    function _setLendingPool(address payable _lendingPool) internal {
        emit SetLendingPool(address(lendingPool), _lendingPool);
        lendingPool = LendingPool(_lendingPool);
    }

    // ==============================================================================
    // Internal Checks
    // ==============================================================================

    /// @notice Checks if an address is the Lending Pool
    /// @param _address Address to test
    function _isLendingPool(address _address) internal view returns (bool) {
        return (_address == address(lendingPool));
    }

    /// @notice Reverts if the address is not the Lending Pool
    /// @param _address Address to test
    function _requireIsLendingPool(address _address) internal view {
        if (!_isLendingPool(_address)) {
            revert AddressIsNotLendingPool(address(lendingPool), _address);
        }
    }

    /// @notice Reverts if msg.sender is not the Lending Pool
    function _requireSenderIsLendingPool() internal view {
        _requireIsLendingPool(msg.sender);
    }

    // ==============================================================================
    // Events
    // ==============================================================================

    /// @notice The ```SetLendingPool``` event fires when the Lending Pool address changes
    /// @param oldLendingPool The old address
    /// @param newLendingPool The new address
    event SetLendingPool(address indexed oldLendingPool, address indexed newLendingPool);

    // ==============================================================================
    // Errors
    // ==============================================================================

    /// @notice Emitted when the test address is not the Lending Pool
    error AddressIsNotLendingPool(address lendingPoolAddress, address actualAddress);
}

File 6 of 44 : OperatorRole.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// =========================== OperatorRole ===========================
// ====================================================================
// Frax Finance: https://github.com/FraxFinance

// Primary Author
// Drake Evans: https://github.com/DrakeEvans

// Reviewers
// Dennis: https://github.com/denett
// Travis Moore: https://github.com/FortisFortuna

// ====================================================================

abstract contract OperatorRole {
    // ============================================================================================
    // Storage & Constructor
    // ============================================================================================

    /// @notice The current operator address
    address public operatorAddress;

    constructor(address _operatorAddress) {
        operatorAddress = _operatorAddress;
    }

    // ============================================================================================
    // Functions: Internal Actions
    // ============================================================================================

    /// @notice The ```OperatorTransferred``` event is emitted when the operator transfer is completed
    /// @param previousOperator The address of the previous operator
    /// @param newOperator The address of the new operator
    event OperatorTransferred(address indexed previousOperator, address indexed newOperator);

    /// @notice The ```_setOperator``` function sets the operator address
    /// @dev This function is to be implemented by a public function
    /// @param _newOperator The address of the new operator
    function _setOperator(address _newOperator) internal {
        emit OperatorTransferred(operatorAddress, _newOperator);
        operatorAddress = _newOperator;
    }

    // ============================================================================================
    // Functions: Internal Checks
    // ============================================================================================

    /// @notice The ```_isOperator``` function checks if _address is current operator address
    /// @param _address The address to check against the operator
    /// @return Whether or not msg.sender is current operator address
    function _isOperator(address _address) internal view returns (bool) {
        return _address == operatorAddress;
    }

    /// @notice The ```AddressIsNotOperator``` error is used for validation of the operatorAddress
    /// @param operatorAddress The expected operatorAddress
    /// @param actualAddress The actual operatorAddress
    error AddressIsNotOperator(address operatorAddress, address actualAddress);

    /// @notice The ```_requireIsOperator``` function reverts if _address is not current operator address
    /// @param _address The address to check against the operator
    function _requireIsOperator(address _address) internal view {
        if (!_isOperator(_address)) revert AddressIsNotOperator(operatorAddress, _address);
    }

    /// @notice The ```_requireSenderIsOperator``` function reverts if msg.sender is not current operator address
    /// @dev This function is to be implemented by a public function
    function _requireSenderIsOperator() internal view {
        _requireIsOperator(msg.sender);
    }
}

File 7 of 44 : SafeERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}

File 8 of 44 : IfrxEthV2AMO.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// Minimum function set that a frxETH_V2 AMO needs to have
interface IfrxEthV2AMO {
    struct ShowAmoBalancedAllocsPacked {
        uint96 amoEthFree;
        uint96 amoEthInLpBalanced;
        uint96 amoEthTotalBalanced;
        uint96 amoFrxEthFree;
        uint96 amoFrxEthInLpBalanced;
    }

    function amoHelper() external view returns (address);

    function depositEther() external payable;

    function requestEtherByRouter(uint256 _ethRequested) external returns (uint256 _ethOut, uint256 _remainingEth);
}

File 9 of 44 : IfrxEthV2AMOHelper.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

interface IfrxEthV2AMOHelper {
    struct PoolInfo {
        bool hasCvxVault; // If there is a cvxLP vault
        bool hasStkCvxFxsVault; // If there is a stkcvxLP vault
        uint8 frxEthIndex; // coins() index of frxETH/sfrxETH
        uint8 ethIndex; // coins() index of ETH/stETH/WETH
        address rewardsContractAddress; // Address for the Convex BaseRewardPool for the cvxLP
        address fxsPersonalVaultAddress; // Address for the stkcvxLP vault, if present
        address poolAddress; // Where the actual tokens are in the pool
        address lpTokenAddress; // The LP token address. Sometimes the same as poolAddress
        address[2] poolCoins; // The addresses of the coins in the pool
        uint32 lpDepositPid; // _convexBaseRewardPool.pid
        LpAbiType lpAbiType; // General pool parameter
        FrxSfrxType frxEthType; // frxETH and sfrxETH
        EthType ethType; // ETH, WETH, and LSDs
        uint256 lpDeposited; // Total LP deposited
        uint256 lpMaxAllocation; // Max LP allowed for this AMO
    }

    struct ShowAmoBalancedAllocsPacked {
        uint96 amoEthFree;
        uint96 amoEthInLpBalanced;
        uint96 amoEthTotalBalanced;
        uint96 amoFrxEthFree;
        uint96 amoFrxEthInLpBalanced;
    }

    enum LpAbiType {
        LSDETH, // frxETH/ETH, rETH/ETH using IPoolLSDETH
        TWOLSDSTABLE, // frxETH/rETH using IPool2LSDStable
        TWOCRYPTO, // ankrETH/frxETH using IPool2Crypto
        LSDWETH // frxETH/WETH using IPoolLSDWETH
    }

    enum FrxSfrxType {
        NONE, // neither frxETH or sfrxETH
        FRXETH, // frxETH
        SFRXETH // sfrxETH
    }

    enum EthType {
        NONE, // ankrETH/frxETH
        RAWETH, // frxETH/ETH
        STETH, // frxETH/stETH
        WETH // frxETH/WETH
    }

    function acceptOwnership() external;

    function calcBalancedFullLPExit(address _curveAmoAddress) external view returns (uint256[2] memory _withdrawables);

    function calcBalancedFullLPExitWithParams(
        address _curveAmo,
        address _poolAddress,
        PoolInfo memory _poolInfo
    ) external view returns (uint256[2] memory _withdrawables);

    function calcMiscBalancedInfo(
        address _curveAmoAddress,
        uint256 _desiredCoinIdx,
        uint256 _desiredCoinAmt
    )
        external
        view
        returns (
            uint256 _lpAmount,
            uint256 _undesiredCoinAmt,
            uint256[2] memory _coinAmounts,
            uint256[2] memory _lpPerCoinsBalancedE18,
            uint256 _lp_virtual_price
        );

    function calcMiscBalancedInfoWithParams(
        address _curveAmoAddress,
        address _poolAddress,
        PoolInfo memory _poolInfo,
        uint256 _desiredCoinIdx,
        uint256 _desiredCoinAmt
    )
        external
        view
        returns (
            uint256 _lpAmount,
            uint256 _undesiredCoinAmt,
            uint256[2] memory _coinAmounts,
            uint256[2] memory _lpPerCoinsBalancedE18,
            uint256 _lp_virtual_price
        );

    function calcOneCoinsFullLPExit(address _curveAmoAddress) external view returns (uint256[2] memory _withdrawables);

    function calcOneCoinsFullLPExitWithParams(
        address _curveAmo,
        address _poolAddress,
        PoolInfo memory _poolInfo
    ) external view returns (uint256[2] memory _withdrawables);

    function calcTknsForLPBalanced(
        address _curveAmoAddress,
        uint256 _lpAmount
    ) external view returns (uint256[2] memory _withdrawables);

    function calcTknsForLPBalancedWithParams(
        address _poolAddress,
        PoolInfo memory _poolInfo,
        uint256 _lpAmount
    ) external view returns (uint256[2] memory _withdrawables);

    function chainlinkEthUsdDecimals() external view returns (uint256);

    function oracleFrxEthUsdDecimals() external view returns (uint256);

    function getCurveInfoPack(
        address _curveAmoAddress
    ) external view returns (address _curveAmo, address _poolAddress, PoolInfo memory _poolInfo);

    function getEstLpPriceEthOrUsdE18(
        address _curveAmoAddress
    ) external view returns (uint256 _inEthE18, uint256 _inUsdE18);

    function getEstLpPriceEthOrUsdE18WithParams(
        address _curveAmo,
        address _poolAddress,
        PoolInfo memory _poolInfo
    ) external view returns (uint256 _inEthE18, uint256 _inUsdE18);

    function getEthPriceE18() external view returns (uint256);

    function getFrxEthPriceE18() external view returns (uint256);

    function lpInVaults(
        address _curveAmoAddress
    ) external view returns (uint256 inCvxRewPool, uint256 inStkCvxFarm, uint256 totalVaultLP);

    function lpInVaultsWithParams(
        address _curveAmo,
        PoolInfo memory _poolInfo
    ) external view returns (uint256 inCvxRewPool, uint256 inStkCvxFarm, uint256 totalVaultLP);

    function owner() external view returns (address);

    function pendingOwner() external view returns (address);

    function priceFeedEthUsd() external view returns (address);

    function priceFeedfrxEthUsd() external view returns (address);

    function renounceOwnership() external;

    function setOracles(address _frxethOracle, address _ethOracle) external;

    function showAllocationsSkipOneCoin(
        address _curveAmoAddress
    ) external view returns (uint256[10] memory _allocations);

    function showAllocationsWithParams(
        address _curveAmo,
        address _poolAddress,
        PoolInfo memory _poolInfo,
        bool _skipOneCoinCalcs
    ) external view returns (uint256[10] memory _allocations);

    function showAmoMaxLP(address _curveAmoAddress) external view returns (uint256 _lpMaxAllocation);

    function showAmoMaxLPWithParams(PoolInfo memory _poolInfo) external view returns (uint256 _lpMaxAllocation);

    function showCVXRewards(address _curveAmoAddress) external view returns (uint256 _cvxRewards);

    function showPoolAccounting(
        address _curveAmoAddress
    )
        external
        view
        returns (uint256[] memory _freeCoinBalances, uint256 _depositedLp, uint256[5] memory _poolAndVaultAllocations);

    function showPoolAccountingWithParams(
        address _curveAmo,
        address _poolAddress,
        PoolInfo memory _poolInfo
    )
        external
        view
        returns (uint256[] memory _freeCoinBalances, uint256 _depositedLp, uint256[5] memory _poolAndVaultAllocations);

    function showPoolFreeCoinBalances(
        address _curveAmoAddress
    ) external view returns (uint256[] memory _freeCoinBalances);

    function showPoolFreeCoinBalancesWithParams(
        address _curveAmoAddress,
        address _poolAddress,
        PoolInfo memory _poolInfo
    ) external view returns (uint256[] memory _freeCoinBalances);

    function showPoolLPTokenAddress(address _curveAmoAddress) external view returns (address _lpTokenAddress);

    function showPoolLPTokenAddressWithParams(
        PoolInfo memory _poolInfo
    ) external view returns (address _lpTokenAddress);

    function showPoolRewards(
        address _curveAmoAddress
    )
        external
        view
        returns (
            uint256 _crvReward,
            uint256[] memory _extraRewardAmounts,
            address[] memory _extraRewardTokens,
            uint256 _extraRewardsLength
        );

    function showPoolRewardsWithParams(
        address _curveAmo,
        PoolInfo memory _poolInfo
    )
        external
        view
        returns (
            uint256 _crvReward,
            uint256[] memory _extraRewardAmounts,
            address[] memory _extraRewardTokens,
            uint256 _extraRewardsLength
        );

    function showPoolVaults(
        address _curveAmoAddress
    ) external view returns (uint256 _lpDepositPid, address _rewardsContractAddress, address _fxsPersonalVaultAddress);

    function showPoolVaultsWithParams(
        PoolInfo memory _poolInfo
    ) external view returns (uint256 _lpDepositPid, address _rewardsContractAddress, address _fxsPersonalVaultAddress);

    function transferOwnership(address newOwner) external;

    function showAllocations(address _curveAmoAddress) external view returns (uint256[10] memory);

    function dollarBalancesOfEths(
        address _curveAmoAddress
    ) external view returns (uint256 frxETHValE18, uint256 ethValE18, uint256 ttlValE18);

    function getConsolidatedEthFrxEthBalance(
        address _curveAmoAddress
    ) external view returns (uint256, uint256, uint256, uint256, uint256);

    function getConsolidatedEthFrxEthBalancePacked(
        address _curveAmoAddress
    ) external view returns (ShowAmoBalancedAllocsPacked memory);
}

File 10 of 44 : PublicReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

// NOTE: This file has been modified from the original to make the _status an internal item so that it can be exposed by consumers.
// This allows us to prevent global reentrancy across different

pragma solidity ^0.8.0;

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

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

    uint256 internal _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

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

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

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

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}

File 11 of 44 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

File 12 of 44 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 13 of 44 : Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

File 14 of 44 : draft-IERC6093.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

File 15 of 44 : FraxEtherRedemptionQueueV2.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ==================== FraxEtherRedemptionQueueV2 ====================
// ====================================================================
// Users wishing to exchange frxETH for ETH 1-to-1 will need to deposit their frxETH and wait to redeem it.
// When they do the deposit, they get an NFT with a maturity time as well as an amount.
// V2: Used in tandem with frxETH V2's Lending Pool

// Frax Finance: https://github.com/FraxFinance

// Primary Author
// Drake Evans: https://github.com/DrakeEvans
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import {
    EtherRouter,
    FraxEtherRedemptionQueueCore,
    FraxEtherRedemptionQueueCoreParams,
    LendingPool,
    SafeCast
} from "./FraxEtherRedemptionQueueCore.sol";

contract FraxEtherRedemptionQueueV2 is FraxEtherRedemptionQueueCore {
    using SafeCast for *;

    constructor(
        FraxEtherRedemptionQueueCoreParams memory _params,
        address payable _etherRouterAddress
    ) FraxEtherRedemptionQueueCore(_params, _etherRouterAddress) {}

    // ==============================================================================
    // FraxEtherRedemptionQueue overrides
    // ==============================================================================

    /// @notice When someone redeems their NFT for ETH, burning it if it is a full redemption
    /// @param nftId the if of the nft redeemed
    /// @param sender the msg.sender
    /// @param recipient the recipient of the ether
    /// @param feeAmt the amount fee kept
    /// @param amountToRedeemer the amount of ether sent to the recipient
    /// @param isPartial If it was a partial redemption
    event NftTicketRedemption(
        uint256 indexed nftId,
        address indexed sender,
        address indexed recipient,
        uint120 feeAmt,
        uint120 amountToRedeemer,
        bool isPartial
    );

    /// @notice Fully redeems a FrxETHRedemptionTicket NFT for ETH. Must have reached the maturity date first.
    /// @param _nftId The ID of the NFT
    /// @param _recipient The recipient of the redeemed ETH
    function fullRedeemNft(
        uint256 _nftId,
        address payable _recipient
    ) external nonReentrant returns (uint120 _amountEtherPaidToUser, uint120 _redemptionFeeAmount) {
        // Add interest
        LendingPool(etherRouter.lendingPool()).addInterestPrivileged(false);

        // Burn the NFT and update the state
        RedemptionQueueItem memory _redemptionQueueItem = _handleRedemptionTicketNftPre(_nftId, 0);

        // Calculations: redemption fee
        _redemptionFeeAmount = ((uint256(_redemptionQueueItem.amount) * uint256(_redemptionQueueItem.redemptionFee)) /
            FEE_PRECISION).toUint120();

        // Calculations: amount of ETH owed to the user
        _amountEtherPaidToUser = _redemptionQueueItem.amount - _redemptionFeeAmount;

        // Calculations: increment unclaimed fees by the redemption fee taken
        redemptionQueueAccounting.unclaimedFees += _redemptionFeeAmount;

        // Calculations: decrement pending fees by the redemption fee taken
        redemptionQueueAccounting.pendingFees -= _redemptionFeeAmount;

        // Effects: Burn frxEth 1:1. Unburnt amount stays as the fee
        FRX_ETH.burn(_amountEtherPaidToUser);

        // If you don't have enough ETH in this contract, pull in the missing amount from the Ether Router
        if (_amountEtherPaidToUser > payable(this).balance) {
            // See how much ETH you actually are missing
            uint256 _missingEth = _amountEtherPaidToUser - payable(this).balance;

            // Pull only what is needed and not the entire RQ shortage
            // If there is still not enough, the entire fullRedeemNft function will revert and the user should try partialRedeemNft
            etherRouter.requestEther(payable(this), _missingEth, true);
        }

        // Effects: Subtract the amount from total liabilities
        // Uses _redemptionQueueItem.amount vs _amountEtherPaidToUser here
        redemptionQueueAccounting.etherLiabilities -= _redemptionQueueItem.amount;

        // Transfer ETH to recipient, minus the fee, if any
        (bool sent, ) = payable(_recipient).call{ value: _amountEtherPaidToUser }("");
        if (!sent) revert InvalidEthTransfer();

        // Update the stored utilization rate
        LendingPool(etherRouter.lendingPool()).updateUtilization();

        emit NftTicketRedemption({
            nftId: _nftId,
            sender: msg.sender,
            recipient: _recipient,
            feeAmt: _redemptionFeeAmount,
            amountToRedeemer: _amountEtherPaidToUser,
            isPartial: false
        });
    }

    /// @notice Partially redeems a FrxETHRedemptionTicket NFT for ETH. Must have reached the maturity date first.
    /// @param _nftId The ID of the NFT
    /// @param _recipient The recipient of the redeemed ETH
    /// @param _redeemAmt The amount you want to redeem
    function partialRedeemNft(uint256 _nftId, address payable _recipient, uint120 _redeemAmt) external nonReentrant {
        // 0 is reserved for full redeems only
        if (_redeemAmt == 0) revert CannotRedeemZero();

        // Add interest
        LendingPool(etherRouter.lendingPool()).addInterestPrivileged(false);

        // Modify the NFT and update the state
        RedemptionQueueItem memory _redemptionQueueItem = _handleRedemptionTicketNftPre(_nftId, _redeemAmt);

        // Calculations: redemption fee
        uint120 _redemptionFeeAmount = ((uint256(_redeemAmt) * uint256(_redemptionQueueItem.redemptionFee)) /
            FEE_PRECISION).toUint120();

        // Calculations: amount of ETH owed to the user
        uint120 _amountEtherOwedToUser = _redeemAmt - _redemptionFeeAmount;

        // Calculations: increment unclaimed fees by the redemption fee taken
        redemptionQueueAccounting.unclaimedFees += _redemptionFeeAmount;

        // Calculations: decrement pending fees by the redemption fee taken
        redemptionQueueAccounting.pendingFees -= _redemptionFeeAmount;

        // Effects: Burn frxEth 1:1. Unburnt amount stays as the fee
        FRX_ETH.burn(_amountEtherOwedToUser);

        // Get the ETH
        // If you don't have enough ETH in this contract, pull in the missing amount from the Ether Router
        if (_amountEtherOwedToUser > payable(this).balance) {
            // See how much ETH you actually are missing
            uint256 _missingEth = _amountEtherOwedToUser - payable(this).balance;

            // Pull only what is needed and not the entire RQ shortage
            // If there is still not enough, the entire partialRedeemNft function will revert here and the user should resubmit with a lower _redeemAmt
            etherRouter.requestEther(payable(this), _missingEth, true);
        }

        // Effects: Subtract the amount from total liabilities
        // Uses _redeemAmt vs _amountEtherOwedToUser here
        redemptionQueueAccounting.etherLiabilities -= _redeemAmt;

        // Transfer ETH to recipient, minus the fee, if any
        (bool sent, ) = payable(_recipient).call{ value: _amountEtherOwedToUser }("");
        if (!sent) revert InvalidEthTransfer();

        // Update the stored utilization rate
        LendingPool(etherRouter.lendingPool()).updateUtilization();

        emit NftTicketRedemption({
            nftId: _nftId,
            sender: msg.sender,
            recipient: _recipient,
            feeAmt: _redemptionFeeAmount,
            amountToRedeemer: _amountEtherOwedToUser,
            isPartial: true
        });
    }

    // ====================================
    // Errors
    // ====================================

    /// @notice Cannot redeem zero
    error CannotRedeemZero();
}

File 16 of 44 : LendingPool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// =========================== LendingPool ============================
// ====================================================================
// Receives and gives out ETH to ValidatorPools for lending and borrowing

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Travis Moore: https://github.com/FortisFortuna
// Drake Evans: https://github.com/DrakeEvans

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { ValidatorPool } from "../ValidatorPool.sol";
import { LendingPoolCore, LendingPoolCoreParams } from "./LendingPoolCore.sol";
import { SSTORE2 } from "solmate/src/utils/SSTORE2.sol";

// import "frax-std/FraxTest.sol";

/// @notice Constructor information for the lending pool
/// @param frxEthAddress Address of the frxETH token
/// @param timelockAddress The address of the governance timelock
/// @param etherRouterAddress The Ether Router address
/// @param beaconOracleAddress The Beacon Oracle address
/// @param redemptionQueueAddress The Redemption Queue address
/// @param interestRateCalculatorAddress Address used for interest rate calculations
/// @param eth2DepositAddress Address of the Eth2 deposit contract
/// @param fullUtilizationRate The interest rate at full utilization
// / @param validatorPoolCreationCode Bytecode for the validator pool (for create2)
struct LendingPoolParams {
    address frxEthAddress;
    address timelockAddress;
    address payable etherRouterAddress;
    address beaconOracleAddress;
    address payable redemptionQueueAddress;
    address interestRateCalculatorAddress;
    address payable eth2DepositAddress;
    uint64 fullUtilizationRate;
}
// bytes validatorPoolCreationCode;

/// @title Receives and gives out ETH to ValidatorPools for lending and borrowing
/// @author Frax Finance
/// @notice Controlled by Frax governance and validator pools
contract LendingPool is LendingPoolCore {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    /// @notice Where the bytecode for the validator pool factory to look at is
    address public validatorPoolCreationCodeAddress;

    // The default credit given to a validator pool, per validator (i.e. per 32 Eth)
    // 12 decimal precision, up to about ~281 Eth
    uint48 public constant DEFAULT_CREDIT_PER_VALIDATOR_I48_E12 = 24e12;

    // The maximum credit given to a validator pool, per validator (i.e. per 32 Eth)
    // 12 decimal precision, up to about ~281 Eth
    uint48 public constant MAXIMUM_CREDIT_PER_VALIDATOR_I48_E12 = 31e12;

    // Fee taken when a validator pool withdraws funds
    uint256 public vPoolWithdrawalFee; // 1e6 precision. Used to help cover slippage, LP fees, and beacon gas
    uint256 public constant MAX_WITHDRAWAL_FEE = 3000; // 0.3%

    /// @notice Constructor
    /// @param _params The LendingPoolParams
    constructor(
        LendingPoolParams memory _params
    )
        LendingPoolCore(
            LendingPoolCoreParams({
                frxEthAddress: _params.frxEthAddress,
                timelockAddress: _params.timelockAddress,
                etherRouterAddress: _params.etherRouterAddress,
                beaconOracleAddress: _params.beaconOracleAddress,
                redemptionQueueAddress: _params.redemptionQueueAddress,
                interestRateCalculatorAddress: _params.interestRateCalculatorAddress,
                eth2DepositAddress: _params.eth2DepositAddress,
                fullUtilizationRate: _params.fullUtilizationRate
            })
        )
    {
        // _setCreationCode(_params.validatorPoolCreationCode);
        _setCreationCode(type(ValidatorPool).creationCode);
    }

    // ==============================================================================
    // Global Configuration Setters
    // ==============================================================================

    // ------------------------------------------------------------------------
    /// @notice When someone tries setting the withdrawal fee above the max (100%)
    /// @param providedFee The provided withdrawal fee
    /// @param maxFee The maximum withdrawal fee
    error ExceedsMaxWithdrawalFee(uint256 providedFee, uint256 maxFee);

    /// @notice When the withdrawal fee for validator pools is set
    /// @param _newFee The new withdrawal fee
    event VPoolWithdrawalFeeSet(uint256 _newFee);

    /// @notice Sets the fee for when a validator pool withdraws
    /// @param _newFee New withdrawal fee given in percentage terms, using 1e6 precision
    /// @dev Mainly used to prevent griefing and handle the Curve LP fees.
    function setVPoolWithdrawalFee(uint256 _newFee) external {
        _requireSenderIsTimelock();
        if (_newFee > MAX_WITHDRAWAL_FEE) revert ExceedsMaxWithdrawalFee(_newFee, MAX_WITHDRAWAL_FEE);

        emit VPoolWithdrawalFeeSet(_newFee);

        vPoolWithdrawalFee = _newFee;
    }

    // ==============================================================================
    // Validator Pool State Setters
    // ==============================================================================

    // ------------------------------------------------------------------------

    /// @notice If the borrow allowance trying to be set is wrong
    error IncorrectBorrowAllowance(uint256 _maxAllowance, uint256 _newAllowance);

    /// @notice When some validator pools have both their total validator counts and/or borrow allowances set
    /// @param _validatorPoolAddresses The addresses of the validator pools
    /// @param _setValidatorCounts Whether to set the validator counts
    /// @param _setBorrowAllowances Whether to set the borrow allowances
    /// @param _newValidatorCounts The new total validator count for each pool
    /// @param _newBorrowAllowances The new borrow allowances for the validators
    /// @param _lastWithdrawalTimestamps validatorPoolAccounts's lastWithdrawal. When this function eventually is called, after a frxGov delay, _lastWithdrawalTimestamps need to match.
    event VPoolValidatorCountsAndBorrowAllowancesSet(
        address[] _validatorPoolAddresses,
        bool _setValidatorCounts,
        bool _setBorrowAllowances,
        uint32[] _newValidatorCounts,
        uint128[] _newBorrowAllowances,
        uint32[] _lastWithdrawalTimestamps
    );

    /// @notice Set the total validator count and/or the borrow allowance for each pool
    /// @param _validatorPoolAddresses The addresses of the validator pools
    /// @param _setValidatorCounts Whether to set the validator counts
    /// @param _setBorrowAllowances Whether to set the borrow allowances
    /// @param _newValidatorCounts The new total validator count for each pool
    /// @param _newBorrowAllowances The new borrow allowances for the validators
    /// @param _lastWithdrawalTimestamps validatorPoolAccounts's lastWithdrawal. When this function eventually is called, after a frxGov delay, _lastWithdrawalTimestamps need to match. Prevents the user from withdrawing immediately after depositing to earn a fake borrow allowance and steal funds.
    function setVPoolValidatorCountsAndBorrowAllowances(
        address[] calldata _validatorPoolAddresses,
        bool _setValidatorCounts,
        bool _setBorrowAllowances,
        uint32[] calldata _newValidatorCounts,
        uint128[] calldata _newBorrowAllowances,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireSenderIsBeaconOracle();

        // Check that the _lastWithdrawalTimestamps array length matches _validatorPoolAddresses
        if (_validatorPoolAddresses.length != _lastWithdrawalTimestamps.length) revert InputArrayLengthMismatch();

        // Check that the _newValidatorCounts array length matches _validatorPoolAddresses
        if (_setValidatorCounts && (_validatorPoolAddresses.length != _newValidatorCounts.length)) {
            revert InputArrayLengthMismatch();
        }

        // Check that the _newBorrowAllowances array length matches _validatorPoolAddresses
        if (_setBorrowAllowances && (_validatorPoolAddresses.length != _newBorrowAllowances.length)) {
            revert InputArrayLengthMismatch();
        }

        // Accumulate interest
        _addInterest(false);

        for (uint256 i = 0; i < _validatorPoolAddresses.length; ) {
            // Fetch the address of the validator pool
            address _validatorPoolAddr = _validatorPoolAddresses[i];

            // Fetch the validator pool account
            ValidatorPoolAccount memory _validatorPoolAccount = validatorPoolAccounts[_validatorPoolAddr];

            // Make sure the validator pool is initialized
            _requireValidatorPoolInitialized(_validatorPoolAddr);

            // Make sure the user did not withdraw in the meantime
            if (_lastWithdrawalTimestamps[i] != _validatorPoolAccount.lastWithdrawal) {
                revert WithdrawalTimestampMismatch(_lastWithdrawalTimestamps[i], _validatorPoolAccount.lastWithdrawal);
            }

            // Set the validators, if specified
            if (_setValidatorCounts) {
                // Set the validator count
                _validatorPoolAccount.validatorCount = _newValidatorCounts[i];
            }

            // Set the borrow allowances, if specified
            if (_setBorrowAllowances) {
                // Calculate the optimistic amount of credit, assuming no borrowing
                uint256 _optimisticAllowance = (uint256(_validatorPoolAccount.validatorCount) *
                    (uint256(_validatorPoolAccount.creditPerValidatorI48_E12) * MISSING_CREDPERVAL_MULT));

                // Calculate the maximum allowance
                uint256 _maxAllowance;
                uint256 _borrowedAmount = toBorrowAmountOptionalRoundUp(_validatorPoolAccount.borrowShares, true);
                if (_optimisticAllowance == 0) {
                    // This may hit if a liquidated user welches on interest if the validator exits are not enough to cover the borrow + interest.
                    _maxAllowance = 0;
                } else if (_borrowedAmount > _optimisticAllowance) {
                    // New allowance should not be negative
                    revert AllowanceWouldBeNegative();
                } else {
                    // Calculate the maximum allowance. Could use unchecked here but meh
                    _maxAllowance = _optimisticAllowance - _borrowedAmount;
                }

                // Revert if you are trying to set above the maximum allowance
                if (_newBorrowAllowances[i] > _maxAllowance) {
                    revert IncorrectBorrowAllowance(_maxAllowance, _newBorrowAllowances[i]);
                }

                // Set the borrow allowance
                _validatorPoolAccount.borrowAllowance = _newBorrowAllowances[i];
            }

            // Write to storage
            validatorPoolAccounts[_validatorPoolAddr] = _validatorPoolAccount;

            // Increment
            unchecked {
                ++i;
            }
        }

        // Update the stored utilization rate
        updateUtilization();

        // Emit
        emit VPoolValidatorCountsAndBorrowAllowancesSet(
            _validatorPoolAddresses,
            _setValidatorCounts,
            _setBorrowAllowances,
            _newValidatorCounts,
            _newBorrowAllowances,
            _lastWithdrawalTimestamps
        );
    }

    // ------------------------------------------------------------------------
    /// @notice When some validator pools have their credits per validator set
    /// @param _validatorPoolAddresses The addresses of the validator pools
    /// @param _newCreditsPerValidator The new total number of credits per validator
    event VPoolCreditsPerPoolSet(address[] _validatorPoolAddresses, uint48[] _newCreditsPerValidator);

    /// @notice Set the amount of Eth credit per validator pool
    /// @param _validatorPoolAddresses The addresses of the validator pools
    /// @param _newCreditsPerValidator The new total number of credits per validator
    function setVPoolCreditsPerValidator(
        address[] calldata _validatorPoolAddresses,
        uint48[] calldata _newCreditsPerValidator
    ) external {
        _requireSenderIsBeaconOracle();

        // Check that the input arrays have the same length
        if (_validatorPoolAddresses.length != _newCreditsPerValidator.length) revert InputArrayLengthMismatch();

        for (uint256 i = 0; i < _validatorPoolAddresses.length; ) {
            // Make sure the validator pool is initialized
            _requireValidatorPoolInitialized(_validatorPoolAddresses[i]);

            // Make sure you are not setting the credit per validator to over MAXIMUM_CREDIT_PER_VALIDATOR_I48_E12 (31 ETH)
            require(
                _newCreditsPerValidator[i] <= MAXIMUM_CREDIT_PER_VALIDATOR_I48_E12,
                "Credit per validator > MAXIMUM_CREDIT_PER_VALIDATOR_I48_E12"
            );

            // Set the credit
            validatorPoolAccounts[_validatorPoolAddresses[i]].creditPerValidatorI48_E12 = _newCreditsPerValidator[i];

            // Increment
            unchecked {
                ++i;
            }
        }

        emit VPoolCreditsPerPoolSet(_validatorPoolAddresses, _newCreditsPerValidator);
    }

    // ------------------------------------------------------------------------
    /// @notice When approval statuses for a multiple validator pubkeys are set
    /// @param _validatorPublicKeys The pubkeys being set
    /// @param _validatorPoolAddresses The validator pools associated with the pubkeys being set
    /// @param _whenApprovedArr When the pubkeys were approved. 0 if they were not
    event VPoolApprovalsSet(bytes[] _validatorPublicKeys, address[] _validatorPoolAddresses, uint32[] _whenApprovedArr);

    /// @notice Set the approval statuses for a multiple validator pubkeys
    /// @param _validatorPublicKeys The pubkeys being set
    /// @param _validatorPoolAddresses The validator pools associated with the pubkeys being set
    /// @param _whenApprovedArr When the pubkeys were approved. 0 if they were not
    /// @param _lastWithdrawalTimestamps validatorPoolAccounts's lastWithdrawal. When this function eventually is called, after a frxGov delay, _lastWithdrawalTimestamps need to match. Prevents the user from withdrawing immediately after depositing to earn a fake borrow allowance and steal funds.
    function setValidatorApprovals(
        bytes[] calldata _validatorPublicKeys,
        address[] calldata _validatorPoolAddresses,
        uint32[] calldata _whenApprovedArr,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireSenderIsBeaconOracle();

        // Check that the input arrays have the same length
        {
            uint256 _arrLength = _validatorPublicKeys.length;
            if (
                (_validatorPoolAddresses.length != _arrLength) ||
                (_whenApprovedArr.length != _arrLength) ||
                (_lastWithdrawalTimestamps.length != _arrLength)
            ) revert InputArrayLengthMismatch();
        }

        for (uint256 i = 0; i < _validatorPublicKeys.length; ) {
            // Fetch the address of the validator pool
            address _validatorPoolAddr = _validatorPoolAddresses[i];

            // Fetch the validator pool account
            ValidatorPoolAccount memory _validatorPoolAccount = validatorPoolAccounts[_validatorPoolAddr];

            // Make sure the user did not withdraw in the meantime
            if (_lastWithdrawalTimestamps[i] != _validatorPoolAccount.lastWithdrawal) {
                revert WithdrawalTimestampMismatch(_lastWithdrawalTimestamps[i], _validatorPoolAccount.lastWithdrawal);
            }

            // Revert if the provided validator pool address doesn't match the first depositor set in initialDepositValidator()
            // It should never be address(0) because the Beacon Oracle check cannot happen before the initial deposit
            if (validatorDepositInfo[_validatorPublicKeys[i]].validatorPoolAddress != _validatorPoolAddr) {
                revert ValidatorPoolKeyMismatch();
            }

            // Set the validator approval state
            validatorDepositInfo[_validatorPublicKeys[i]].whenValidatorApproved = _whenApprovedArr[i];

            // Increment
            unchecked {
                ++i;
            }
        }

        emit VPoolApprovalsSet(_validatorPublicKeys, _validatorPoolAddresses, _whenApprovedArr);
    }

    // ==============================================================================
    // Validator Pool Factory Functions
    // ==============================================================================

    /// @notice The ```setCreationCode``` function sets the bytecode for the ValidatorPool
    /// @dev splits the data if necessary to accommodate creation code that is slightly larger than 24kb
    /// @param _creationCode The creationCode for the ValidatorPool
    function setCreationCode(bytes memory _creationCode) external {
        _requireSenderIsTimelock();
        _setCreationCode(_creationCode);
    }

    /// @notice The ```setCreationCode``` function sets the bytecode for the ValidatorPool
    /// @dev splits the data if necessary to accommodate creation code that is slightly larger than 24kb
    /// @param _creationCode The creationCode for the ValidatorPool
    function _setCreationCode(bytes memory _creationCode) internal {
        validatorPoolCreationCodeAddress = SSTORE2.write(_creationCode);
    }

    // ------------------------------------------------------------------------
    /// @notice When a validator pool is created
    /// @param _validatorPoolOwnerAddress The owner of the validator pool
    /// @return _poolAddress The address of the validator pool that was created
    event VPoolDeployed(address _validatorPoolOwnerAddress, address _poolAddress);

    /// @notice Deploy a validator pool (callable by anyone)
    /// @param _validatorPoolOwnerAddress The owner of the validator pool
    /// @param _extraSalt An extra salt bytes32 provided by the user
    /// @return _poolAddress The address of the validator pool that was created
    function deployValidatorPool(
        address _validatorPoolOwnerAddress,
        bytes32 _extraSalt
    ) public returns (address payable _poolAddress) {
        // Get creation code
        bytes memory _creationCode = SSTORE2.read(validatorPoolCreationCodeAddress);

        // Get bytecode
        bytes memory bytecode = abi.encodePacked(
            _creationCode,
            abi.encode(_validatorPoolOwnerAddress, payable(address(this)), payable(address(ETH2_DEPOSIT_CONTRACT)))
        );

        bytes32 _salt = keccak256(abi.encodePacked(msg.sender, _validatorPoolOwnerAddress, _extraSalt));

        /// @solidity memory-safe-assembly
        assembly {
            _poolAddress := create2(0, add(bytecode, 32), mload(bytecode), _salt)
        }
        if (_poolAddress == address(0)) revert("create2 failed");

        // Mark validator pool as approved
        validatorPoolAccounts[_poolAddress].isInitialized = true;
        validatorPoolAccounts[_poolAddress].creditPerValidatorI48_E12 = DEFAULT_CREDIT_PER_VALIDATOR_I48_E12;

        emit VPoolDeployed(_validatorPoolOwnerAddress, _poolAddress);
    }

    // ==============================================================================
    // Preview Interest Functions
    // ==============================================================================

    /// @notice Get information about a validator pool
    /// @param _validatorPoolAddress The validator pool in question
    function previewValidatorAccounts(
        address _validatorPoolAddress
    ) external view returns (ValidatorPoolAccount memory) {
        return validatorPoolAccounts[_validatorPoolAddress];
    }

    // ==============================================================================
    // Reentrancy View Function
    // ==============================================================================

    /// @notice Get the entrancy status
    /// @return _isEntered If the contract has already been entered
    function entrancyStatus() external view returns (bool _isEntered) {
        _isEntered = _status == 2;
    }
}

File 17 of 44 : IERC20Permit.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 18 of 44 : Address.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}

File 19 of 44 : FraxEtherRedemptionQueueCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ===================== FraxEtherRedemptionQueue =====================
// ====================================================================
// Users wishing to exchange frxETH for ETH 1-to-1 will need to deposit their frxETH and wait to redeem it.
// When they do the deposit, they get an NFT with a maturity time as well as an amount.

// Frax Finance: https://github.com/FraxFinance

// Primary Author
// Drake Evans: https://github.com/DrakeEvans
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { PublicReentrancyGuard } from "frax-std/access-control/v2/PublicReentrancyGuard.sol";
import { LendingPool } from "src/contracts/lending-pool/LendingPool.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { EtherRouter } from "../ether-router/EtherRouter.sol";
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { OperatorRole } from "frax-std/access-control/v2/OperatorRole.sol";
import { IFrxEth } from "./interfaces/IFrxEth.sol";
import { ISfrxEth } from "./interfaces/ISfrxEth.sol";

/// @notice Used by the constructor
/// @param timelockAddress Address of the timelock, which the main owner of the this contract
/// @param operatorAddress Address of the operator, which does other tasks
/// @param frxEthAddress Address of frxEth Erc20
/// @param sfrxEthAddress Address of sfrxEth Erc20
/// @param initialQueueLengthSecondss Initial length of the queue, in seconds
struct FraxEtherRedemptionQueueCoreParams {
    address timelockAddress;
    address operatorAddress;
    address frxEthAddress;
    address sfrxEthAddress;
    uint32 initialQueueLengthSeconds;
}

contract FraxEtherRedemptionQueueCore is ERC721, Timelock2Step, OperatorRole, PublicReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeCast for *;

    // ==============================================================================
    // Storage
    // ==============================================================================

    // Contracts
    // ================
    /// @notice The Ether Router
    EtherRouter public immutable etherRouter;

    // Tokens
    // ================
    /// @notice The frxETH token
    IFrxEth public immutable FRX_ETH;

    /// @notice The sfrxETH token
    ISfrxEth public immutable SFRX_ETH;

    // Version
    // ================
    string public version = "1.0.2";

    // Queue-Related
    // ================
    /// @notice State of Frax's frxETH redemption queue
    /// @param etherLiabilities How much ETH is currently under request to be redeemed
    /// @param nextNftId Autoincrement for the NFT id
    /// @param queueLengthSecs Current wait time (in seconds) a new redeemer would have. Should be close to Beacon.
    /// @param redemptionFee Redemption fee given as a percentage with 1e6 precision
    /// @param ttlEthRequested Cumulative total amount of ETH requested for redemption
    /// @param ttlEthServed Cumulative total amount of ETH and/or frxETH actually sent back to redeemers. ETH in the case of a mature redeem
    struct RedemptionQueueState {
        uint64 nextNftId;
        uint64 queueLengthSecs;
        uint64 redemptionFee;
        uint120 ttlEthRequested;
        uint120 ttlEthServed;
    }

    /// @notice State of Frax's frxETH redemption queue
    RedemptionQueueState public redemptionQueueState;

    /// @param etherLiabilities How much ETH would need to be paid out if every NFT holder could claim immediately
    /// @param unclaimedFees Earned fees that the protocol has not collected yet
    /// @param pendingFees Amount of fees expected if all outstanding NFTs were redeemed fully
    struct RedemptionQueueAccounting {
        uint120 etherLiabilities;
        uint120 unclaimedFees;
        uint120 pendingFees;
    }

    /// @notice Accounting of Frax's frxETH redemption queue
    RedemptionQueueAccounting public redemptionQueueAccounting;

    /// @notice Information about a user's redemption ticket NFT
    mapping(uint256 nftId => RedemptionQueueItem) public nftInformation;

    /// @notice The ```RedemptionQueueItem``` struct provides metadata information about each Nft
    /// @param hasBeenRedeemed boolean for whether the NFT has been redeemed
    /// @param amount How much ETH is claimable
    /// @param maturity Unix timestamp when they can claim their ETH
    /// @param redemptionFee redemptionFee (E6) at time of NFT mint
    /// @param ttlEthRequestedSnapshot ttlEthServed + (available ETH) must be >= ttlEthRequestedSnapshot. ttlEthRequestedSnapshot is redemptionQueueState.ttlEthRequested + (the amount of ETH you put in your redemption request) at the time of the enterRedemptionQueue call
    struct RedemptionQueueItem {
        bool hasBeenRedeemed;
        uint64 maturity;
        uint120 amount;
        uint64 redemptionFee;
        uint120 ttlEthRequestedSnapshot;
    }

    /// @notice Maximum queue length the operator can set, given in seconds
    uint256 public maxQueueLengthSeconds = 100 days;

    /// @notice Precision of the redemption fee
    uint64 public constant FEE_PRECISION = 1e6;

    /// @notice Maximum settable fee for redeeming
    uint64 public constant MAX_REDEMPTION_FEE = 20_000; // 2% max

    /// @notice Maximum amount of frxETH that can be used to create an NFT
    /// @dev If it were too large, the user could get stuck for a while until loans get paid back, or more people deposit ETH for frxETH
    uint120 public constant MAX_FRXETH_PER_NFT = 1000 ether;

    /// @notice The fee recipient for various fees
    address public feeRecipient;

    // ==============================================================================
    // Constructor
    // ==============================================================================

    /// @notice Constructor
    /// @param _params The contructor FraxEtherRedemptionQueueCoreParams params
    constructor(
        FraxEtherRedemptionQueueCoreParams memory _params,
        address payable _etherRouterAddress
    )
        payable
        ERC721("FrxETH Redemption Queue Ticket V2", "FrxETHRedemptionTicketV2")
        OperatorRole(_params.operatorAddress)
        Timelock2Step(_params.timelockAddress)
    {
        // Initialize some state variables
        if (_params.initialQueueLengthSeconds > maxQueueLengthSeconds) {
            revert ExceedsMaxQueueLengthSecs(_params.initialQueueLengthSeconds, maxQueueLengthSeconds);
        }
        redemptionQueueState.queueLengthSecs = _params.initialQueueLengthSeconds;
        FRX_ETH = IFrxEth(_params.frxEthAddress);
        SFRX_ETH = ISfrxEth(_params.sfrxEthAddress);
        etherRouter = EtherRouter(_etherRouterAddress);

        // Default the fee recipient to the operator (can be changed later)
        feeRecipient = _params.operatorAddress;
    }

    /// @notice Allows contract to receive Eth
    receive() external payable {
        // Do nothing except take in the Eth
    }

    // =============================================================================================
    // Configurations / Privileged functions
    // =============================================================================================

    /// @notice When the accrued redemption fees are collected
    /// @param recipient The address to receive the fees
    /// @param collectAmount Amount of fees collected
    event CollectRedemptionFees(address recipient, uint120 collectAmount);

    /// @notice Collect all redemption fees (in frxETH)
    function collectAllRedemptionFees() external returns (uint120 _collectedAmount) {
        // Call the internal function
        return _collectRedemptionFees(0, true);
    }

    /// @notice Collect a specified amount of redemption fees (in frxETH)
    /// @param _collectAmount Amount of frxEth to collect
    function collectRedemptionFees(uint120 _collectAmount) external returns (uint120 _collectedAmount) {
        // Call the internal function
        _collectRedemptionFees(_collectAmount, false);
    }

    /// @notice Collect redemption fees (in frxETH). Fees go to the fee recipient address
    /// @param _collectAmount Amount of frxEth to collect.
    /// @param _collectAllOverride If true, _collectAmount is overriden with redemptionQueueAccounting.unclaimedFees and all available fees are collected
    function _collectRedemptionFees(
        uint120 _collectAmount,
        bool _collectAllOverride
    ) internal returns (uint120 _collectedAmount) {
        // Make sure the sender is either the timelock, operator, or fee recipient
        _requireIsTimelockOperatorOrFeeRecipient();

        // Get the amount of unclaimed fees
        uint120 _unclaimedFees = redemptionQueueAccounting.unclaimedFees;

        // See if there is the override
        if (_collectAllOverride) _collectAmount = _unclaimedFees;

        // Make sure you are not taking too much
        if (_collectAmount > _unclaimedFees) revert ExceedsCollectedFees(_collectAmount, _unclaimedFees);

        // Decrement the unclaimed fee amount
        redemptionQueueAccounting.unclaimedFees -= _collectAmount;

        // Interactions: Transfer frxEth fees to the recipient
        IERC20(address(FRX_ETH)).safeTransfer({ to: feeRecipient, value: _collectAmount });

        emit CollectRedemptionFees({ recipient: feeRecipient, collectAmount: _collectAmount });

        return _collectAmount;
    }

    /// @notice When the timelock or operator recovers ERC20 tokens mistakenly sent here
    /// @param recipient Address of the recipient
    /// @param token Address of the erc20 token
    /// @param amount Amount of the erc20 token recovered
    event RecoverErc20(address recipient, address token, uint256 amount);

    /// @notice Recovers ERC20 tokens mistakenly sent to this contract
    /// @param _tokenAddress Address of the token
    /// @param _tokenAmount Amount of the token
    function recoverErc20(address _tokenAddress, uint256 _tokenAmount) external {
        _requireSenderIsTimelock();
        IERC20(_tokenAddress).safeTransfer({ to: msg.sender, value: _tokenAmount });
        emit RecoverErc20({ recipient: msg.sender, token: _tokenAddress, amount: _tokenAmount });
    }

    /// @notice The EtherRecovered event is emitted when recoverEther is called
    /// @param recipient Address of the recipient
    /// @param amount Amount of the ether recovered
    event RecoverEther(address recipient, uint256 amount);

    /// @notice Recover ETH when someone mistakenly directly sends ETH here
    /// @param _amount Amount of ETH to recover
    function recoverEther(uint256 _amount) external {
        _requireSenderIsTimelock();

        (bool _success, ) = address(msg.sender).call{ value: _amount }("");
        if (!_success) revert InvalidEthTransfer();

        emit RecoverEther({ recipient: msg.sender, amount: _amount });
    }

    /// @notice When the redemption fee is set
    /// @param oldRedemptionFee Old redemption fee
    /// @param newRedemptionFee New redemption fee
    event SetRedemptionFee(uint64 oldRedemptionFee, uint64 newRedemptionFee);

    /// @notice Sets the fee for redeeming
    /// @param _newFee New redemption fee given in percentage terms, using 1e6 precision
    function setRedemptionFee(uint64 _newFee) external {
        _requireSenderIsTimelock();
        if (_newFee > MAX_REDEMPTION_FEE) revert ExceedsMaxRedemptionFee(_newFee, MAX_REDEMPTION_FEE);

        emit SetRedemptionFee({ oldRedemptionFee: redemptionQueueState.redemptionFee, newRedemptionFee: _newFee });

        redemptionQueueState.redemptionFee = _newFee;
    }

    /// @notice When the current wait time (in seconds) of the queue is set
    /// @param oldQueueLength Old queue length in seconds
    /// @param newQueueLength New queue length in seconds
    event SetQueueLengthSeconds(uint64 oldQueueLength, uint64 newQueueLength);

    /// @notice Sets the current wait time (in seconds) a new redeemer would have
    /// @param _newLength New queue time, in seconds
    function setQueueLengthSeconds(uint64 _newLength) external {
        _requireIsTimelockOrOperator();
        if (msg.sender != timelockAddress && _newLength > maxQueueLengthSeconds) {
            revert ExceedsMaxQueueLengthSecs(_newLength, maxQueueLengthSeconds);
        }

        emit SetQueueLengthSeconds({
            oldQueueLength: redemptionQueueState.queueLengthSecs,
            newQueueLength: _newLength
        });

        redemptionQueueState.queueLengthSecs = _newLength;
    }

    /// @notice When the max queue length the operator can set is changed
    /// @param oldMaxQueueLengthSecs Old max queue length in seconds
    /// @param newMaxQueueLengthSecs New max queue length in seconds
    event SetMaxQueueLengthSeconds(uint256 oldMaxQueueLengthSecs, uint256 newMaxQueueLengthSecs);

    /// @notice Sets the maximum queue length the operator can set
    /// @param _newMaxQueueLengthSeconds New maximum queue length
    function setMaxQueueLengthSeconds(uint256 _newMaxQueueLengthSeconds) external {
        _requireSenderIsTimelock();

        emit SetMaxQueueLengthSeconds({
            oldMaxQueueLengthSecs: maxQueueLengthSeconds,
            newMaxQueueLengthSecs: _newMaxQueueLengthSeconds
        });

        maxQueueLengthSeconds = _newMaxQueueLengthSeconds;
    }

    /// @notice Sets the operator (bot) that updates the queue length
    /// @param _newOperator New bot address
    function setOperator(address _newOperator) external {
        _requireSenderIsTimelock();
        _setOperator(_newOperator);
    }

    /// @notice When the fee recipient is set
    /// @param oldFeeRecipient Old fee recipient address
    /// @param newFeeRecipient New fee recipient address
    event SetFeeRecipient(address oldFeeRecipient, address newFeeRecipient);

    /// @notice Where redemption fees go
    /// @param _newFeeRecipient New fee recipient address
    function setFeeRecipient(address _newFeeRecipient) external {
        _requireSenderIsTimelock();

        emit SetFeeRecipient({ oldFeeRecipient: feeRecipient, newFeeRecipient: _newFeeRecipient });

        feeRecipient = _newFeeRecipient;
    }

    // ==============================================================================
    // Helper views
    // ==============================================================================

    /// @notice See if you can redeem the given NFT.
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    /// @param _partialAmount The partial amount you want to redeem. Leave as 0 for a full redemption test
    /// @param _revertIfFalse If true, will revert if false
    /// @return _isRedeemable If the NFT can be redeemed with the specified _partialAmount
    /// @return _maxAmountRedeemable The max amount you can actually redeem. Will be <= your full position amount. May be 0 if your queue position or something else is wrong.
    function canRedeem(
        uint256 _nftId,
        uint120 _partialAmount,
        bool _revertIfFalse
    ) public view returns (bool _isRedeemable, uint120 _maxAmountRedeemable) {
        // Get NFT information
        RedemptionQueueItem memory _redemptionQueueItem = nftInformation[_nftId];

        // Different routes depending on the _partialAmount input
        if (_partialAmount > 0) {
            // Call the internal function
            (_isRedeemable, _maxAmountRedeemable) = _canRedeem(_redemptionQueueItem, _partialAmount, _revertIfFalse);
        } else {
            // Call the internal function
            (_isRedeemable, _maxAmountRedeemable) = _canRedeem(
                _redemptionQueueItem,
                _redemptionQueueItem.amount,
                _revertIfFalse
            );
        }
    }

    /// @notice See if you can partially redeem the given NFT.
    /// @param _redemptionQueueItem The ID of the FrxEthRedemptionTicket NFT
    /// @param _amountRequested The amount you want to redeem
    /// @param _revertIfFalse If true, will revert if false. Otherwise returns a boolean
    /// @return _isRedeemable If the NFT can be redeemed with the specified _amountRequested
    /// @return _maxAmountRedeemable The max amount you can actually redeem. Will be <= your full position amount. May be 0 if your queue position or something else is wrong.
    /// @dev A partial redeem can not be used to 'cut' in line for the queue. Your queue position is always as if you tried to redeem fully
    function _canRedeem(
        RedemptionQueueItem memory _redemptionQueueItem,
        uint120 _amountRequested,
        bool _revertIfFalse
    ) internal view returns (bool _isRedeemable, uint120 _maxAmountRedeemable) {
        // Check Maturity
        // -----------------------------------------------------------
        // See if the maturity has been reached and it hasn't already been redeemed
        if (block.timestamp >= _redemptionQueueItem.maturity && !_redemptionQueueItem.hasBeenRedeemed) {
            // So far so good
            _isRedeemable = true;
        } else {
            // Either revert or mark _isRedeemable as false
            if (_revertIfFalse) {
                revert NotMatureYet({ currentTime: block.timestamp, maturity: _redemptionQueueItem.maturity });
            } else {
                // Return early
                return (false, 0);
            }
        }

        // Check for full redeem
        // Special case if _amountRequested is 0, then set it to _redemptionQueueItem.amount
        if (_amountRequested == 0) _amountRequested = _redemptionQueueItem.amount;

        // Calculate how much ETH is present and/or pullable
        // -----------------------------------------------------------
        // Get the actual amount of ETH needed, accounting for the fee
        uint120 _amountReqMinusFee = _amountRequested -
            ((uint256(_amountRequested) * uint256(_redemptionQueueItem.redemptionFee)) / FEE_PRECISION).toUint120();

        // Get the ETH balance in this contract
        uint120 _localBal = uint120(address(this).balance);

        // Get the amount of ETH pullable from the Ether Router
        EtherRouter.CachedConsEFxBalances memory _cachedBals = etherRouter.getConsolidatedEthFrxEthBalanceView(true);
        uint120 _pullableBal = uint120(_cachedBals.ethTotalBalanced);
        uint120 _availableBal = _localBal + _pullableBal;

        // See if enough is present and/or pullable to satisfy the NFT
        // -----------------------------------------------------------

        // If the NFT amount is more than the local Eth and the pullable Eth, you cannot redeem
        if (_amountReqMinusFee > _availableBal) {
            // Either revert or mark _isRedeemable as false
            if (_revertIfFalse) {
                revert InsufficientEth({ requested: _amountReqMinusFee, available: _availableBal });
            } else {
                // Don't return yet
                _isRedeemable = false;
            }
        }

        // Check queue position.
        // -----------------------------------------------------------
        // Get queue information
        RedemptionQueueState memory _redemptionQueueState = redemptionQueueState;

        // What ttlEthServed would be if everyone redeemed who could, with the available balance from contracts, AMOs, etc.
        uint120 _maxTtlEthServed = _redemptionQueueState.ttlEthServed + _availableBal;

        // The max amount of ETH that can be used to serve YOU specifically
        uint120 _maxTtlEthServeableToYou;
        if (_maxTtlEthServed >= _redemptionQueueItem.ttlEthRequestedSnapshot) {
            _maxTtlEthServeableToYou = _maxTtlEthServed - _redemptionQueueItem.ttlEthRequestedSnapshot;
        } else {
            (_maxTtlEthServeableToYou = 0);
        }

        // _amountReqMinusFee must be <= _maxTtlEthServeableToYou
        if (_amountReqMinusFee <= _maxTtlEthServeableToYou) {
            // Do nothing since _isRedeemable is already true
        } else {
            // Either revert or mark _isRedeemable as false
            if (_revertIfFalse) {
                revert QueuePosition({
                    ttlEthRequestedSnapshot: _redemptionQueueItem.ttlEthRequestedSnapshot,
                    requestedAmount: _amountReqMinusFee,
                    maxTtlEthServed: _maxTtlEthServed
                });
            } else {
                // Don't return yet
                _isRedeemable = false;
            }
        }

        // Update _maxAmountRedeemable
        // -----------------------------------------------------------
        // For starters, it should never be more than your actual position
        _maxAmountRedeemable = _redemptionQueueItem.amount;

        // Lower _maxAmountRedeemable if there isn't enough ETH
        if (_maxAmountRedeemable > _availableBal) _maxAmountRedeemable = _availableBal;

        // Lower _maxAmountRedeemable again if there is some ETH, but you cannot have it because others are in front of you
        if (_maxAmountRedeemable > _maxTtlEthServeableToYou) _maxAmountRedeemable = _maxTtlEthServeableToYou;

        // You cannot request more than you are entitled too
        // -----------------------------------------------------------

        // See if you are requesting more than you should
        if (_amountRequested > _redemptionQueueItem.amount) {
            // Either revert or mark _isRedeemable as false
            if (_revertIfFalse) {
                revert RedeemingTooMuch({ requested: _amountRequested, entitledTo: _redemptionQueueItem.amount });
            } else {
                // Don't return yet
                _isRedeemable = false;
            }
        }
    }

    /// @notice Get the entrancy status
    /// @return _isEntered If the contract has already been entered
    function entrancyStatus() external view returns (bool _isEntered) {
        _isEntered = _status == 2;
    }

    /// @notice How much shortage or surplus (to cover upcoming redemptions) this contract has
    /// @return _netEthBalance int256 Positive or negative balance of ETH
    /// @return _shortage uint256 The remaining amount of ETH needed to cover all redemptions. 0 if there is no shortage or a surplus.
    function ethShortageOrSurplus() external view returns (int256 _netEthBalance, uint256 _shortage) {
        // // Protect against reentrancy (not entered yet)
        // require(_status == 1, "ethShortageOrSurplus reentrancy");

        // Current ETH balance of this contract
        int256 _currBalance = int256(address(this).balance);

        // Total amount of ETH needed to cover all outstanding redemptions
        int256 _currLiabilities = int256(uint256(redemptionQueueAccounting.etherLiabilities));

        // Subtract pending fees since these technically will part of a surplus
        _currLiabilities -= int256(uint256(redemptionQueueAccounting.pendingFees));

        // Calculate the shortage or surplus
        _netEthBalance = _currBalance - _currLiabilities;

        // If there is a shortage, convert it to uint256
        if (_netEthBalance < 0) _shortage = uint256(-_netEthBalance);
    }

    // =============================================================================================
    // Queue Functions
    // =============================================================================================

    /// @notice When someone enters the redemption queue
    /// @param nftId The ID of the NFT
    /// @param sender The address of the msg.sender, who is redeeming frxEth
    /// @param recipient The recipient of the NFT
    /// @param amountFrxEthRedeemed The amount of frxEth requested to be redeemed
    /// @param maturityTimestamp The date of maturity, upon which redemption is allowed
    /// @param redemptionFee The redemption fee (E6) at the time of minting
    /// @param ttlEthRequestedSnapshot ttlEthRequested + amountFrxEthRedeemed at the time of the enterRedemptionQueue
    event EnterRedemptionQueue(
        uint256 indexed nftId,
        address indexed sender,
        address indexed recipient,
        uint256 amountFrxEthRedeemed,
        uint64 maturityTimestamp,
        uint64 redemptionFee,
        uint120 ttlEthRequestedSnapshot
    );

    /// @notice Enter the queue for redeeming frxEth 1-to-1 for Eth, without the need to approve first (EIP-712 / EIP-2612)
    /// @notice Will generate a FrxEthRedemptionTicket NFT that can be redeemed for the actual Eth later.
    /// @param _amountToRedeem Amount of frxETH to redeem. Must be < MAX_FRXETH_PER_NFT
    /// @param _recipient Recipient of the NFT. Must be ERC721 compatible if a contract
    /// @param _deadline Deadline for this signature
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    function enterRedemptionQueueWithPermit(
        uint120 _amountToRedeem,
        address _recipient,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (uint256 _nftId) {
        // Call the permit
        FRX_ETH.permit({
            owner: msg.sender,
            spender: address(this),
            value: _amountToRedeem,
            deadline: _deadline,
            v: _v,
            r: _r,
            s: _s
        });

        // Do the redemption
        _nftId = enterRedemptionQueue({ _recipient: _recipient, _amountToRedeem: _amountToRedeem });
    }

    /// @notice Enter the queue for redeeming sfrxEth to frxETH at the current rate, then frxETH to Eth 1-to-1, without the need to approve first (EIP-712 / EIP-2612)
    /// @notice Will generate a FrxEthRedemptionTicket NFT that can be redeemed for the actual Eth later.
    /// @param _sfrxEthAmount Amount of sfrxETH to redeem (in shares / balanceOf). Resultant frxETH amount must be < MAX_FRXETH_PER_NFT
    /// @param _recipient Recipient of the NFT. Must be ERC721 compatible if a contract
    /// @param _deadline Deadline for this signature
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    function enterRedemptionQueueWithSfrxEthPermit(
        uint120 _sfrxEthAmount,
        address _recipient,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (uint256 _nftId) {
        // Call the permit
        SFRX_ETH.permit({
            owner: msg.sender,
            spender: address(this),
            value: _sfrxEthAmount,
            deadline: _deadline,
            v: _v,
            r: _r,
            s: _s
        });

        // Do the redemption
        _nftId = enterRedemptionQueueViaSfrxEth({ _recipient: _recipient, _sfrxEthAmount: _sfrxEthAmount });
    }

    /// @notice Enter the queue for redeeming sfrxEth to frxETH at the current rate, then frxETH to ETH 1-to-1. Must have approved or permitted first.
    /// @notice Will generate a FrxETHRedemptionTicket NFT that can be redeemed for the actual ETH later.
    /// @param _recipient Recipient of the NFT. Must be ERC721 compatible if a contract
    /// @param _sfrxEthAmount Amount of sfrxETH to redeem (in shares / balanceOf). Resultant frxETH amount must be < MAX_FRXETH_PER_NFT
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    /// @dev Must call approve/permit on frxEth contract prior to this call
    function enterRedemptionQueueViaSfrxEth(
        address _recipient,
        uint120 _sfrxEthAmount
    ) public returns (uint256 _nftId) {
        // Pull in the sfrxETH
        SFRX_ETH.transferFrom({ from: msg.sender, to: address(this), amount: uint256(_sfrxEthAmount) });

        // Exchange the sfrxETH for frxETH
        uint256 _frxEthAmount = SFRX_ETH.redeem(_sfrxEthAmount, address(this), address(this));

        // Enter the queue with the frxETH you just obtained
        _nftId = _enterRedemptionQueueCore(_recipient, uint120(_frxEthAmount));
    }

    /// @notice Enter the queue for redeeming frxETH 1-to-1. Must approve first. Internal only so payor can be set
    /// @notice Will generate a FrxETHRedemptionTicket NFT that can be redeemed for the actual ETH later.
    /// @param _recipient Recipient of the NFT. Must be ERC721 compatible if a contract
    /// @param _amountToRedeem Amount of frxETH to redeem.
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    /// @dev Must call approve/permit on frxEth contract prior to this call
    function _enterRedemptionQueueCore(
        address _recipient,
        uint120 _amountToRedeem
    ) internal nonReentrant returns (uint256 _nftId) {
        // Don't allow too much frxETH per NFT, otherwise it can get hard to redeem later if borrow activity is high
        if (_amountToRedeem > MAX_FRXETH_PER_NFT) revert ExceedsMaxFrxEthPerNFT();

        // Add interest
        LendingPool(etherRouter.lendingPool()).addInterestPrivileged(false);

        // Get queue information
        RedemptionQueueState memory _redemptionQueueState = redemptionQueueState;
        RedemptionQueueAccounting memory _redemptionQueueAccounting = redemptionQueueAccounting;

        // Calculations: increment ether liabilities by the amount of ether owed to the user
        _redemptionQueueAccounting.etherLiabilities += _amountToRedeem;

        // Calculations: increment pending fees that will eventually be taken
        _redemptionQueueAccounting.pendingFees += ((uint256(_amountToRedeem) *
            uint256(_redemptionQueueState.redemptionFee)) / FEE_PRECISION).toUint120();

        // Calculations: maturity timestamp
        uint64 _maturityTimestamp = uint64(block.timestamp) + _redemptionQueueState.queueLengthSecs;

        // Effects: Initialize the redemption ticket NFT information
        nftInformation[_redemptionQueueState.nextNftId] = RedemptionQueueItem({
            amount: _amountToRedeem,
            maturity: _maturityTimestamp,
            hasBeenRedeemed: false,
            redemptionFee: _redemptionQueueState.redemptionFee,
            ttlEthRequestedSnapshot: _redemptionQueueState.ttlEthRequested // pre-increment
        });

        // Effects: Mint the redemption ticket NFT. Make sure the recipient supports ERC721.
        _safeMint({ to: _recipient, tokenId: _redemptionQueueState.nextNftId });

        // Emit here, before the state change
        _nftId = _redemptionQueueState.nextNftId;
        emit EnterRedemptionQueue({
            nftId: _nftId,
            sender: msg.sender,
            recipient: _recipient,
            amountFrxEthRedeemed: _amountToRedeem,
            maturityTimestamp: _maturityTimestamp,
            redemptionFee: _redemptionQueueState.redemptionFee,
            ttlEthRequestedSnapshot: _redemptionQueueState.ttlEthRequested // pre-increment
        });

        // Calculations: Increment the ttlEthRequested.
        _redemptionQueueState.ttlEthRequested += _amountToRedeem;

        // Calculations: Increment the autoincrement
        ++_redemptionQueueState.nextNftId;

        // Effects: Write all of the state changes to storage
        redemptionQueueState = _redemptionQueueState;

        // Effects: Write all of the accounting changes to storage
        redemptionQueueAccounting = _redemptionQueueAccounting;

        // Update the stored utilization rate
        LendingPool(etherRouter.lendingPool()).updateUtilization();
    }

    /// @notice Enter the queue for redeeming frxETH 1-to-1. Must approve or permit first.
    /// @notice Will generate a FrxETHRedemptionTicket NFT that can be redeemed for the actual ETH later.
    /// @param _recipient Recipient of the NFT. Must be ERC721 compatible if a contract
    /// @param _amountToRedeem Amount of frxETH to redeem. Must be < MAX_FRXETH_PER_NFT
    /// @param _nftId The ID of the FrxEthRedemptionTicket NFT
    /// @dev Must call approve/permit on frxEth contract prior to this call
    function enterRedemptionQueue(address _recipient, uint120 _amountToRedeem) public returns (uint256 _nftId) {
        // Do all of the NFT-generating and accounting logic
        _nftId = _enterRedemptionQueueCore(_recipient, _amountToRedeem);

        // Interactions: Transfer frxEth in from the sender
        IERC20(address(FRX_ETH)).safeTransferFrom({ from: msg.sender, to: address(this), value: _amountToRedeem });
    }

    /// @notice Redeems a FrxETHRedemptionTicket NFT for ETH. (Pre-ETH send)
    /// @param _nftId The ID of the NFT
    /// @param _redeemAmt The amount to redeem
    /// @return _redemptionQueueItem The RedemptionQueueItem
    function _handleRedemptionTicketNftPre(
        uint256 _nftId,
        uint120 _redeemAmt
    ) internal returns (RedemptionQueueItem memory _redemptionQueueItem) {
        // Checks: ensure proper NFT ownership
        if (!_isAuthorized({ owner: _requireOwned(_nftId), spender: msg.sender, tokenId: _nftId })) {
            revert Erc721CallerNotOwnerOrApproved();
        }

        // Get NFT information
        _redemptionQueueItem = nftInformation[_nftId];

        // Checks: Make sure maturity was reached
        // Will revert if it was not
        _canRedeem(_redemptionQueueItem, _redeemAmt, true);

        // Different paths for full vs partial
        if (_redeemAmt == 0 || _redeemAmt == _redemptionQueueItem.amount) {
            // Full Redeem
            // ---------------------------------------

            // Effects: burn the NFT
            _burn(_nftId);

            // Effects: Increment the ttlEthServed
            // Not including fees here so ttlEthRequested gets canceled out
            redemptionQueueState.ttlEthServed += _redemptionQueueItem.amount;

            // Effects: Zero the amount remaining in the NFT
            nftInformation[_nftId].amount = 0;

            // Effects: Mark NFT as redeemed
            nftInformation[_nftId].hasBeenRedeemed = true;
        } else {
            // Partial Redeem
            // ---------------------------------------

            // Effects: Increment the ttlEthServed
            // Not including fees here so ttlEthRequested gets canceled out
            redemptionQueueState.ttlEthServed += _redeemAmt;

            // Effects: Lower amount remaining in the NFT
            nftInformation[_nftId].amount -= _redeemAmt;
        }

        // IMPORTANT!!!
        // NOTE: Make sure redemptionQueueAccounting.etherLiabilities is accounted for somewhere down the line

        // IMPORTANT!!!
        // NOTE: Make sure to burn the frxETH somewhere down the line
    }

    // ====================================
    // Internal Functions
    // ====================================

    /// @notice Checks if msg.sender is current timelock address or the operator
    function _requireIsTimelockOrOperator() internal view {
        if (!((msg.sender == timelockAddress) || (msg.sender == operatorAddress))) revert NotTimelockOrOperator();
    }

    /// @notice Checks if msg.sender is current timelock address, operator, or fee recipient
    function _requireIsTimelockOperatorOrFeeRecipient() internal view {
        if (!((msg.sender == timelockAddress) || (msg.sender == operatorAddress) || (msg.sender == feeRecipient))) {
            revert NotTimelockOperatorOrFeeRecipient();
        }
    }

    /// @notice ERC721: caller is not token owner or approved
    error Erc721CallerNotOwnerOrApproved();

    /// @notice When timelock/operator tries collecting more fees than they are due
    /// @param collectAmount How much fee the ounsender is trying to collect
    /// @param accruedAmount How much fees are actually collectable
    error ExceedsCollectedFees(uint128 collectAmount, uint128 accruedAmount);

    /// @notice When someone tries setting the queue length above the max
    /// @param providedLength The provided queue length
    /// @param maxLength The maximum queue length
    error ExceedsMaxQueueLengthSecs(uint64 providedLength, uint256 maxLength);

    /// @notice When someone tries to create a redemption NFT using too much frxETH
    error ExceedsMaxFrxEthPerNFT();

    /// @notice When someone tries setting the redemption fee above MAX_REDEMPTION_FEE
    /// @param providedFee The provided redemption fee
    /// @param maxFee The maximum redemption fee
    error ExceedsMaxRedemptionFee(uint64 providedFee, uint64 maxFee);

    /// @notice Not enough ETH locally + Ether Router + AMOs to do the redemption
    /// @param available The amount of ETH actually available
    /// @param requested The amount of ETH requested
    error InsufficientEth(uint120 requested, uint120 available);

    /// @notice Invalid ETH transfer during recoverEther
    error InvalidEthTransfer();

    /// @notice NFT is not mature enough to redeem yet
    /// @param currentTime Current time.
    /// @param maturity Time of maturity
    error NotMatureYet(uint256 currentTime, uint64 maturity);

    /// @notice Thrown if the sender is not the timelock, operator, or fee recipient
    error NotTimelockOperatorOrFeeRecipient();

    /// @notice Thrown if the sender is not the timelock or the operator
    error NotTimelockOrOperator();

    /// @notice Other (earlier) people are ahead of you in the queue. ttlEthServed + (available ETH) must be >= ttlEthRequestedSnapshot + requestedAmount
    /// @param ttlEthRequestedSnapshot The NFT's snapshot of ttlEthRequested
    /// @param requestedAmount The actual amount being requested
    /// @param maxTtlEthServed What ttlEthServed would be if everyone redeemed who could, with the available balance from contracts, AMOs, etc.
    error QueuePosition(uint120 ttlEthRequestedSnapshot, uint120 requestedAmount, uint120 maxTtlEthServed);

    /// @notice When you try to redeem more than the NFT entitles you to
    /// @param requested The amount of ETH requested
    /// @param entitledTo The amount of ETH the NFT entitles you to
    error RedeemingTooMuch(uint120 requested, uint120 entitledTo);
}

File 20 of 44 : ValidatorPool.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// =========================== ValidatorPool ==========================
// ====================================================================
// Deposits ETH to earn collateral credit for borrowing on the LendingPool
// Controlled by the depositor

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { PublicReentrancyGuard } from "frax-std/access-control/v2/PublicReentrancyGuard.sol";
import { Ownable, Ownable2Step } from "@openzeppelin/contracts/access/Ownable2Step.sol";
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { ILendingPool } from "./lending-pool/interfaces/ILendingPool.sol";
import { IDepositContract } from "./interfaces/IDepositContract.sol";

// import { console } from "frax-std/FraxTest.sol";
// import { Logger } from "frax-std/Logger.sol";

/// @title Deposits ETH to earn collateral credit for borrowing on the LendingPool
/// @author Frax Finance
/// @notice Controlled by the depositor
contract ValidatorPool is Ownable2Step, PublicReentrancyGuard {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    /// @notice Track amount of ETH sent to deposit contract, by pubkey
    mapping(bytes validatorPubKey => uint256 amtDeposited) public depositedAmts;

    /// @notice Withdrawal creds for the validators
    bytes32 public immutable withdrawalCredentials;

    /// @notice The Eth lending pool
    ILendingPool public immutable lendingPool;

    /// @notice The official Eth2 deposit contract
    IDepositContract public immutable ETH2_DEPOSIT_CONTRACT;

    /// @notice Constructor
    /// @param _ownerAddress The owner of the validator pool
    /// @param _lendingPoolAddress Address of the lending pool
    /// @param _eth2DepositAddress Address of the Eth2 deposit contract
    constructor(
        address _ownerAddress,
        address payable _lendingPoolAddress,
        address payable _eth2DepositAddress
    ) Ownable(_ownerAddress) {
        lendingPool = ILendingPool(_lendingPoolAddress);
        bytes32 _bitMask = 0x0100000000000000000000000000000000000000000000000000000000000000;
        bytes32 _address = bytes32(uint256(uint160(address(this))));
        withdrawalCredentials = _bitMask | _address;

        ETH2_DEPOSIT_CONTRACT = IDepositContract(_eth2DepositAddress);
    }

    // ==============================================================================
    // Eth Handling
    // ==============================================================================

    /// @notice Accept Eth
    receive() external payable {}

    // ==============================================================================
    // Check Functions
    // ==============================================================================

    /// @notice Make sure the sender is the validator pool owner
    function _requireSenderIsOwner() internal view {
        if (msg.sender != owner()) revert SenderMustBeOwner();
    }

    /// @notice Make sure the sender is either the validator pool owner or the owner
    function _requireSenderIsOwnerOrLendingPool() internal view {
        if (msg.sender == owner() || msg.sender == address(lendingPool)) {
            // Do nothing
        } else {
            revert SenderMustBeOwnerOrLendingPool();
        }
    }

    /// @notice Make sure the supplied pubkey has been used (deposited to) by this validator before
    /// @param _pubKey The pubkey you want to test
    function _requireValidatorIsUsed(bytes memory _pubKey) internal view {
        if (depositedAmts[_pubKey] == 0) revert ValidatorIsNotUsed();
    }

    // ==============================================================================
    // View Functions
    // ==============================================================================
    /// @notice Get the amount of Eth borrowed by this validator pool (live)
    /// @param _amtEthBorrowed The amount of ETH this pool has borrowed
    function getAmountBorrowed() public view returns (uint256 _amtEthBorrowed) {
        // Calculate the amount borrowed after adding interest
        (, _amtEthBorrowed, ) = lendingPool.wouldBeSolvent(address(this), true, 0, 0);
    }

    /// @notice Get the amount of Eth borrowed by this validator pool. May be stale if LendingPool.addInterest has not been called for a while
    /// @return _amtEthBorrowed The amount of ETH this pool has borrowed
    /// @return _sharesBorrowed The amount of shares this pool has borrowed
    function getAmountAndSharesBorrowedStored() public view returns (uint256 _amtEthBorrowed, uint256 _sharesBorrowed) {
        // Fetch the borrowShares
        (, , , , , , _sharesBorrowed) = lendingPool.validatorPoolAccounts(address(this));

        // Return the amount of ETH borrowed
        _amtEthBorrowed = lendingPool.toBorrowAmountOptionalRoundUp(_sharesBorrowed, true);
    }

    // ==============================================================================
    // Deposit Functions
    // ==============================================================================

    /// @notice When the validator pool makes a deposit
    /// @param _validatorPool The validator pool making the deposit
    /// @param _pubkey Public key of the validator.
    /// @param _amount Amount of Eth being deposited
    /// @dev The ETH2 emits a Deposit event, but this is for Beacon Oracle / offchain tracking help
    event ValidatorPoolDeposit(address _validatorPool, bytes _pubkey, uint256 _amount);

    /// @notice Deposit a specified amount of ETH into the ETH2 deposit contract
    /// @param pubkey Public key of the validator
    /// @param signature Signature from the validator
    /// @param _depositDataRoot Part of the deposit message
    /// @param _depositAmount The amount to deposit
    function _deposit(
        bytes calldata pubkey,
        bytes calldata signature,
        bytes32 _depositDataRoot,
        uint256 _depositAmount
    ) internal {
        bytes memory _withdrawalCredentials = abi.encodePacked(withdrawalCredentials);
        // Deposit one batch
        ETH2_DEPOSIT_CONTRACT.deposit{ value: _depositAmount }(
            pubkey,
            _withdrawalCredentials,
            signature,
            _depositDataRoot
        );

        // Increment the amount deposited
        depositedAmts[pubkey] += _depositAmount;

        emit ValidatorPoolDeposit(address(this), pubkey, _depositAmount);
    }

    // /// @notice Deposit 32 ETH into the ETH2 deposit contract
    // /// @param pubkey Public key of the validator
    // /// @param signature Signature from the validator
    // /// @param _depositDataRoot Part of the deposit message
    // function fullDeposit(
    //     bytes calldata pubkey,
    //     bytes calldata signature,
    //     bytes32 _depositDataRoot
    // ) external payable nonReentrant {
    //     _requireSenderIsOwner();

    //     // Deposit the ether in the ETH 2.0 deposit contract
    //     // Use this contract's stored withdrawal_credentials
    //     require((msg.value + address(this).balance) >= 32 ether, "Need 32 ETH");
    //     _deposit(pubkey, signature, _depositDataRoot, 32 ether);

    //     lendingPool.initialDepositValidator(pubkey, 32 ether);
    // }

    // /// @notice Deposit a partial amount of ETH into the ETH2 deposit contract
    // /// @param _validatorPublicKey Public key of the validator
    // /// @param _validatorSignature Signature from the validator
    // /// @param _depositDataRoot Part of the deposit message
    // /// @dev This is not a full deposit and will have to be completed later
    // function partialDeposit(
    //     bytes calldata _validatorPublicKey,
    //     bytes calldata _validatorSignature,
    //     bytes32 _depositDataRoot
    // ) external payable nonReentrant {
    //     _requireSenderIsOwner();

    //     // Deposit the ether in the ETH 2.0 deposit contract
    //     require((msg.value + address(this).balance) >= 8 ether, "Need 8 ETH");
    //     _deposit(_validatorPublicKey, _validatorSignature, _depositDataRoot, 8 ether);

    //     lendingPool.initialDepositValidator(_validatorPublicKey, 8 ether);
    // }

    /// @notice Deposit ETH into the ETH2 deposit contract. Only msg.value / sender funds can be used
    /// @param _validatorPublicKey Public key of the validator
    /// @param _validatorSignature Signature from the validator
    /// @param _depositDataRoot Part of the deposit message
    /// @dev Forcing msg.value only prevents users from seeding an external validator and depositing exited funds into there,
    /// which they can then further exit and steal
    function deposit(
        bytes calldata _validatorPublicKey,
        bytes calldata _validatorSignature,
        bytes32 _depositDataRoot
    ) external payable nonReentrant {
        _requireSenderIsOwner();

        // Make sure an integer amount of 1 Eth is being deposited
        // Avoids a case where < 1 Eth is borrowed to finalize a deposit, only to have it fail at the Eth 2.0 contract
        // Also avoids the 1 gwei minimum increment issue at the Eth 2.0 contract
        if ((msg.value % (1 ether)) != 0) revert MustBeIntegerMultipleOf1Eth();

        // Deposit the ether in the ETH 2.0 deposit contract
        // This will reject if the deposit amount isn't at least 1 ETH + a multiple of 1 gwei
        _deposit(_validatorPublicKey, _validatorSignature, _depositDataRoot, msg.value);

        // Register the deposit with the lending pool
        // Will revert if you go over 32 ETH
        lendingPool.initialDepositValidator(_validatorPublicKey, msg.value);
    }

    /// @notice Finalizes an incomplete ETH2 deposit made earlier, borrowing any remainder from the lending pool
    /// @param _validatorPublicKey Public key of the validator
    /// @param _validatorSignature Signature from the validator
    /// @param _depositDataRoot Part of the deposit message
    /// @dev You don't necessarily need credit here because the collateral is secured by the exit message. You pay the interest rate.
    /// Not part of the normal borrow credit system, this is separate.
    /// Useful for leveraging your position if the borrow rate is low enough
    function requestFinalDeposit(
        bytes calldata _validatorPublicKey,
        bytes calldata _validatorSignature,
        bytes32 _depositDataRoot
    ) external nonReentrant {
        _requireSenderIsOwner();
        _requireValidatorIsUsed(_validatorPublicKey);

        // Reverts if deposits not allowed or Validator Pool does not have enough credit/allowance
        lendingPool.finalDepositValidator(
            _validatorPublicKey,
            abi.encodePacked(withdrawalCredentials),
            _validatorSignature,
            _depositDataRoot
        );
    }

    // ==============================================================================
    // Borrow Functions
    // ==============================================================================

    /// @notice Borrow ETH from the Lending Pool and give to the recipient
    /// @param _recipient Recipient of the borrowed funds
    /// @param _borrowAmount Amount being borrowed
    function borrow(address payable _recipient, uint256 _borrowAmount) public nonReentrant {
        _requireSenderIsOwner();

        // Borrow ETH from the Lending Pool and give to the recipient
        lendingPool.borrow(_recipient, _borrowAmount);
    }

    // ==============================================================================
    // Repay Functions
    // ==============================================================================

    // /// @notice Repay a loan with sender's msg.value ETH
    // /// @dev May have a Zeno's paradox situation where repay -> dust accumulates interest -> repay -> dustier dust accumulates interest
    // /// @dev So use repayAllWithPoolAndValue
    // function repayWithValue() external payable nonReentrant {
    //     // On liquidation lending pool will call this function to repay the debt
    //     _requireSenderIsOwnerOrLendingPool();

    //     // Take ETH from the sender and give to the Lending Pool to repay any loans
    //     lendingPool.repay{ value: msg.value }(address(this));
    // }

    // /// @notice Repay a loan, specifing the ETH amount using the contract's own ETH
    // /// @param _repayAmount Amount of ETH to repay
    // /// @dev May have a Zeno's paradox situation where repay -> dust accumulates interest -> repay -> dustier dust accumulates interest
    // /// @dev So use repayAllWithPoolAndValue
    // function repayAmount(uint256 _repayAmount) external nonReentrant {
    //     // On liquidation lending pool will call this function to repay the debt
    //     _requireSenderIsOwnerOrLendingPool();

    //     // Take ETH from this contract and give to the Lending Pool to repay any loans
    //     lendingPool.repay{ value: _repayAmount }(address(this));
    // }

    /// @notice Repay a loan, specifing the shares amount. Uses this contract's own ETH
    /// @param _repayShares Amount of shares to repay
    function repayShares(uint256 _repayShares) external nonReentrant {
        _requireSenderIsOwnerOrLendingPool();
        uint256 _repayAmount = lendingPool.toBorrowAmountOptionalRoundUp(_repayShares, true);
        lendingPool.repay{ value: _repayAmount }(address(this));
    }

    /// @notice Repay a loan using pool ETH, msg.value ETH, or both. Will revert if overpaying
    /// @param _vPoolAmountToUse Amount of validator pool ETH to use
    /// @dev May have a Zeno's paradox situation where repay -> dust accumulates interest -> repay -> dustier dust accumulates interest
    /// @dev So use repayAllWithPoolAndValue in that case
    function repayWithPoolAndValue(uint256 _vPoolAmountToUse) external payable nonReentrant {
        // On liquidation lending pool will call this function to repay the debt
        _requireSenderIsOwnerOrLendingPool();

        // Take ETH from this contract and msg.sender and give it to the Lending Pool to repay any loans
        lendingPool.repay{ value: _vPoolAmountToUse + msg.value }(address(this));
    }

    /// @notice Repay an ENTIRE loan using pool ETH, msg.value ETH, or both. Will revert if overpaying msg.value
    function repayAllWithPoolAndValue() external payable nonReentrant {
        // On liquidation lending pool will call this function to repay the debt
        _requireSenderIsOwnerOrLendingPool();

        // Calculate the true amount borrowed after adding interest
        (, uint256 _remainingBorrow, ) = lendingPool.wouldBeSolvent(address(this), true, 0, 0);

        // Repay with msg.value first. Will revert if overpaying
        if (msg.value > 0) {
            // Repay with all of the msg.value provided
            lendingPool.repay{ value: msg.value }(address(this));

            // Update _remainingBorrow
            _remainingBorrow -= msg.value;
        }

        // Repay any leftover with VP ETH. Will revert if insufficient.
        lendingPool.repay{ value: _remainingBorrow }(address(this));
    }

    // ==============================================================================
    // Withdraw Functions
    // ==============================================================================

    /// @notice Withdraw ETH from this contract. Must not have any outstanding loans.
    /// @param _recipient Recipient of the ETH
    /// @param _withdrawAmount Amount to withdraw
    /// @dev Even assuming the exited ETH is dumped back in here before the Beacon Oracle registers that, and if the user
    /// tried to borrow again, their collateral would be this exited ETH now that is "trapped" until the loan is repaid,
    /// rather than being in a validator, so it is still ok. borrow() would increase borrowShares, which would still need to be paid off first
    function withdraw(address payable _recipient, uint256 _withdrawAmount) external nonReentrant {
        _requireSenderIsOwner();

        // Calculate the withdrawal fee amount
        uint256 _withdrawalFeeAmt = (_withdrawAmount * lendingPool.vPoolWithdrawalFee()) / 1e6;
        uint256 _postFeeAmt = _withdrawAmount - _withdrawalFeeAmt;

        // Register the withdrawal on the lending pool
        // Will revert unless all debts are paid off first
        lendingPool.registerWithdrawal(_recipient, _postFeeAmt, _withdrawalFeeAmt);

        // Give the fee to the Ether Router first, to cover any fees/slippage from LP movements
        (bool sent, ) = payable(lendingPool.etherRouter()).call{ value: _withdrawalFeeAmt }("");
        if (!sent) revert InvalidEthTransfer();

        // Withdraw ETH from this validator pool and give to the recipient
        (sent, ) = payable(_recipient).call{ value: _postFeeAmt }("");
        if (!sent) revert InvalidEthTransfer();
    }

    // ==============================================================================
    // Errors
    // ==============================================================================

    /// @notice External contract should not have been entered previously
    error ExternalContractAlreadyEntered();

    /// @notice Invalid ETH transfer during recoverEther
    error InvalidEthTransfer();

    /// @notice When you are trying to deposit a non integer multiple of 1 ether
    error MustBeIntegerMultipleOf1Eth();

    /// @notice Sender must be the lending pool
    error SenderMustBeLendingPool();

    /// @notice Sender must be the owner
    error SenderMustBeOwner();

    /// @notice Sender must be the owner or the lendingPool
    error SenderMustBeOwnerOrLendingPool();

    /// @notice Validator is not approved
    error ValidatorIsNotUsed();

    /// @notice Wrong Ether deposit amount
    error WrongEthDepositAmount();
}

File 21 of 44 : LendingPoolCore.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================= LendingPoolCore ==========================
// ====================================================================
// Recieves and gives out ETH to ValidatorPools for lending and borrowing (core code)

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { ValidatorPool } from "../ValidatorPool.sol";
import { VaultAccount, VaultAccountingLibrary } from "../libraries/VaultAccountingLibrary.sol";
import { BeaconOracle } from "../BeaconOracle.sol";
import { BeaconOracle, BeaconOracleRole } from "../access-control/BeaconOracleRole.sol";
import { EtherRouter, EtherRouterRole } from "../access-control/EtherRouterRole.sol";
import { FraxEtherRedemptionQueueV2, RedemptionQueueV2Role } from "../access-control/RedemptionQueueV2Role.sol";
import { IFrxEth } from "../interfaces/IFrxEth.sol";
import { IInterestRateCalculator } from "./IInterestRateCalculator.sol";
import { PublicReentrancyGuard } from "frax-std/access-control/v2/PublicReentrancyGuard.sol";
// import { console } from "frax-std/FraxTest.sol";
import { IDepositContract } from "../interfaces/IDepositContract.sol";

/// @notice Constructor information for the lending pool core
/// @param frxEthAddress Address of the frxETH token
/// @param timelockAddress The address of the governance timelock
/// @param etherRouterAddress The Ether Router address
/// @param beaconOracleAddress The Beacon Oracle address
/// @param redemptionQueueAddress The Redemption Queue address
/// @param interestRateCalculatorAddress Address used for interest rate calculations
/// @param eth2DepositAddress Address of the Eth2 deposit contract
/// @param fullUtilizationRate The interest rate at full utilization
struct LendingPoolCoreParams {
    address frxEthAddress;
    address timelockAddress;
    address payable etherRouterAddress;
    address beaconOracleAddress;
    address payable redemptionQueueAddress;
    address interestRateCalculatorAddress;
    address payable eth2DepositAddress;
    uint64 fullUtilizationRate;
}

/// @title Recieves and gives out ETH to ValidatorPools for lending and borrowing
/// @author Frax Finance
/// @notice Controlled by Frax governance and validator pools
abstract contract LendingPoolCore is
    EtherRouterRole,
    BeaconOracleRole,
    RedemptionQueueV2Role,
    Timelock2Step,
    PublicReentrancyGuard
{
    using SafeCast for uint256;
    using VaultAccountingLibrary for VaultAccount;

    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    /// @notice frxETH
    IFrxEth public immutable frxETH;

    /// @notice The official Eth2 deposit contract
    IDepositContract public immutable ETH2_DEPOSIT_CONTRACT;

    /// @notice Precision for the utilization ratio
    uint256 public constant UTILIZATION_PRECISION = 1e5;

    /// @notice Precision for the interest rate
    uint256 public constant INTEREST_RATE_PRECISION = 1e18;

    /// @notice Total amount of ETH currently borrowed
    VaultAccount public totalBorrow;

    /// @notice Total amount of ETH interest accrued from lending out the ETH
    uint256 public interestAccrued;

    /// @notice Stored utilization rate, to mitigate manipulation. Updated with addInterest.
    uint256 public utilizationStored;

    /// @notice Contract for interest rate calculations
    IInterestRateCalculator public rateCalculator;

    /// @notice Multiplier for credits per vault calculations
    uint256 public immutable MISSING_CREDPERVAL_MULT = 1e6;

    /// @notice Minimum borrow amount (used to help with share rounding / prevent share manipulation)
    uint256 public constant MINIMUM_BORROW_AMOUNT = 1000 gwei;

    /// @notice ValidatorPool state information
    /// @param isInitialialized If the validator pool is initialized
    /// @param wasLiquidated If the validator pool is currently being liquidated
    /// @param lastWithdrawal The last time the validator pool made a withdrawal
    /// @param validatorCount The number of validators the pool has
    /// @param creditPerValidatorI48_E12 The amount of lending credit per validator. 12 decimals of precision. Max is ~281e12
    /// @param borrowShares How many shares the pool is currently borrowing
    struct ValidatorPoolAccount {
        bool isInitialized;
        bool wasLiquidated;
        uint32 lastWithdrawal;
        uint32 validatorCount;
        uint48 creditPerValidatorI48_E12;
        uint128 borrowAllowance;
        uint256 borrowShares;
    }

    /// @notice Validator pool account information
    mapping(address _validatorPool => ValidatorPoolAccount) public validatorPoolAccounts;

    /// @notice ValidatorPool pubkey deposit information
    /// @param whenValidatorApproved When the pubkey was approved by the beacon oracle. 0 if it was not
    /// @param wasFullDepositOrFinalized If the pubkey was either a full 32 ETH deposit, or if it was a partial that was finalized.
    /// @param validatorPoolAddress The validator pool associated with the pubkey
    /// @param userDepositedEther The amount of Eth the validator pool contributed. Will be less than 32 Eth for a partial deposit
    /// @param lendingPoolDepositedEther The amount of Eth the lending pool loaned to complete this deposit. Will be > 0 for a partial deposit.
    /// @dev Useful for tracking full vs partial deposits
    struct ValidatorDepositInfo {
        uint32 whenValidatorApproved;
        bool wasFullDepositOrFinalized;
        address validatorPoolAddress;
        uint96 userDepositedEther;
        uint96 lendingPoolDepositedEther;
    }

    /// @notice Validator pool deposit information
    mapping(bytes _validatorPublicKey => ValidatorDepositInfo) public validatorDepositInfo;

    /// @notice Current interest rate information (storage variable)
    CurrentRateInfo public currentRateInfo;

    /// @notice Current interest rate information (struct)
    /// @param lastTimestamp Timestamp of the last state update
    /// @param ratePerSec Interest rate, in e18 per second
    /// @param fullUtilizationRate The rate at full utilization
    struct CurrentRateInfo {
        uint64 lastTimestamp;
        uint64 ratePerSec;
        uint64 fullUtilizationRate;
    }

    /// @notice Allowed liquidators
    mapping(address _addr => bool _canLiquidate) public isLiquidator;

    // ==============================================================================
    // Constructor
    // ==============================================================================

    /// @notice Constructor
    /// @param _params The LendingPoolCoreParams
    constructor(
        LendingPoolCoreParams memory _params
    )
        Timelock2Step(_params.timelockAddress)
        RedemptionQueueV2Role(_params.redemptionQueueAddress)
        EtherRouterRole(_params.etherRouterAddress)
        BeaconOracleRole(_params.beaconOracleAddress)
    {
        frxETH = IFrxEth(_params.frxEthAddress);
        rateCalculator = IInterestRateCalculator(_params.interestRateCalculatorAddress);
        currentRateInfo.fullUtilizationRate = _params.fullUtilizationRate;
        currentRateInfo.lastTimestamp = uint64(block.timestamp - 1);

        ETH2_DEPOSIT_CONTRACT = IDepositContract(_params.eth2DepositAddress);
    }

    // ==============================================================================
    // Check functions
    // ==============================================================================

    /// @notice Reverts if the pubkey is not associated with the validator pool address supplied
    /// @param _address The address of the validator pool that should be associated with the pubkey
    /// @param _publicKey The pubkey to check
    function _requireAddressAssociatedWithPubkey(address _address, bytes calldata _publicKey) internal view {
        if (validatorDepositInfo[_publicKey].validatorPoolAddress != _address) revert ValidatorPoolKeyMismatch();
    }

    /// @notice Reverts if the address cannot liquidate
    /// @param _address The address to check
    /// @dev Either an allowed liquidator, the timelock, or the beacon oracle
    function _requireAddressCanLiquidate(address _address) internal view {
        if (!(isLiquidator[_address] || (_address == timelockAddress) || (_address == address(beaconOracle)))) {
            revert NotAllowedLiquidator();
        }
    }

    /// @notice Checks if msg.sender is the ether router or the redemption queue
    function _requireSenderIsEtherRouterOrRedemptionQueue() internal view {
        if (!((msg.sender == address(etherRouter)) || (msg.sender == address(redemptionQueue)))) {
            revert NotEtherRouterOrRedemptionQueue();
        }
    }

    /// @notice Reverts if the validator pubkey is not approved
    /// @param _publicKey The pubkey to check
    function _requireValidatorApproved(bytes calldata _publicKey) internal view {
        if (!isValidatorApproved(_publicKey)) revert ValidatorIsNotApprovedLP();
    }

    /// @notice Reverts if the validator pubkey is not initialized
    /// @param _publicKey The pubkey to check
    function _requireValidatorInitialized(bytes calldata _publicKey) internal view {
        if (validatorDepositInfo[_publicKey].userDepositedEther == 0) revert ValidatorIsNotInitialized();
    }

    /// @notice Reverts if the validator pool is not initialized
    /// @param _address The address of the validator pool to check
    function _requireValidatorPoolInitialized(address _address) internal view {
        if (!validatorPoolAccounts[_address].isInitialized) revert InvalidValidatorPool();
    }

    /// @notice Reverts if the validator pool is insolvent
    /// @param _validatorPool The validator pool address
    function _requireValidatorPoolIsSolvent(address _validatorPool) internal view {
        if (!isSolvent(_validatorPool)) revert ValidatorPoolIsNotSolvent();
    }

    /// @notice Reverts if the validator pool is in liquidation
    /// @param _address The address of the validator pool to check
    function _requireValidatorPoolNotLiquidated(address _address) internal view {
        if (validatorPoolAccounts[_address].wasLiquidated) revert ValidatorPoolWasLiquidated();
    }

    // ==============================================================================
    // Helper Functions
    // ==============================================================================

    /// @notice Get the last withdrawal time for an address
    /// @param _validatorPoolAddress The validator pool being looked up
    /// @return _lastWithdrawalTimestamp The timestamp of the last withdrawal
    function getLastWithdrawalTimestamp(
        address _validatorPoolAddress
    ) public view returns (uint32 _lastWithdrawalTimestamp) {
        // Get the timestamp
        _lastWithdrawalTimestamp = validatorPoolAccounts[_validatorPoolAddress].lastWithdrawal;
    }

    /// @notice Get the last withdrawal times for a given set of addresses
    /// @param _validatorPoolAddresses The validator pools being looked up
    /// @return _lastWithdrawalTimestamps The timestamps of the last withdrawals
    function getLastWithdrawalTimestamps(
        address[] calldata _validatorPoolAddresses
    ) public view returns (uint32[] memory _lastWithdrawalTimestamps) {
        // Initialize the return array
        _lastWithdrawalTimestamps = new uint32[](_validatorPoolAddresses.length);

        // Loop through the addresses
        // --------------------------------------------------------
        for (uint256 i = 0; i < _validatorPoolAddresses.length; ) {
            // Add the timestamp to the return array
            _lastWithdrawalTimestamps[i] = validatorPoolAccounts[_validatorPoolAddresses[i]].lastWithdrawal;

            unchecked {
                ++i;
            }
        }
    }

    /// @notice Return the current utilization
    /// @param _cachedBals AMO values from getConsolidatedEthFrxEthBalance
    /// @param _skipRQReentrantCheck True to disable checking RedemptionQueue reentrancy. Only should be True for addInterestPrivileged calls
    /// @return _utilization The current utilization
    /// @dev ETH in LP on the Curve AMO is considered "utilized" and thus a "liability"
    function _getUtilizationPostCore(
        EtherRouter.CachedConsEFxBalances memory _cachedBals,
        bool _skipRQReentrantCheck
    ) internal view returns (uint256 _utilization) {
        // console.log("_getUtilizationPostCore: PART 0");

        // Check for reentrancy
        if (!_skipRQReentrantCheck && redemptionQueue.entrancyStatus()) revert ReentrancyStatusIsTrue();

        // console.log("_getUtilizationPostCore: PART 1");

        // Check the shortage or surplus of ETH in the redemption queue
        (int256 _netEthBalance, ) = redemptionQueue.ethShortageOrSurplus();

        // console.log("_getUtilizationPostCore: PART 2");

        // Return 100% utilization if there would be an underflow due to an ETH shortage in the redemption queue
        int256 denominator = int256(totalBorrow.amount) +
            int256(uint256(_cachedBals.ethTotalBalanced)) +
            _netEthBalance;
        if (denominator <= 0) {
            // console.log("_getUtilizationPostCore: PART 2B");
            return UTILIZATION_PRECISION;
        }

        // console.log("_getUtilizationPostCore (numerator): %s", totalBorrow.amount * UTILIZATION_PRECISION);
        // console.log("_getUtilizationPostCore (totalBorrow.amount): %s", totalBorrow.amount);
        // console.log("_getUtilizationPostCore (_cachedBals.ethTotalBalanced): %s", _cachedBals.ethTotalBalanced);
        // console.log("_getUtilizationPostCore (_netEthBalance): %s", _netEthBalance);
        // console.log("_getUtilizationPostCore (denominator): %s", denominator);
        // console.log("_getUtilizationPostCore: PART 3");
        // Calculate the utilization
        _utilization = (totalBorrow.amount * UTILIZATION_PRECISION) / (uint256(denominator));

        // console.log("_utilization (uncapped): %s", _utilization);
        // console.log("_getUtilizationPostCore: PART 4");
        // Cap the utilization at 100%
        if (_utilization > UTILIZATION_PRECISION) _utilization = UTILIZATION_PRECISION;

        // console.log("_getUtilizationPostCore: PART 5");
    }

    /// @notice Return the current utilization. Calculates live AMO values. Should only be called internally
    /// @param _forceLive Force a live recalculation of the AMO values
    /// @param _updateCache Update the cached AMO values, if they were stale
    /// @param _skipRQReentrantCheck True to disable checking RedemptionQueue reentrancy. Only should be True for addInterestPrivileged calls
    /// @return _utilization The current utilization
    /// @dev ETH in LP on the Curve AMO is considered "utilized" and thus a "liability"
    function _getUtilizationInternal(
        bool _forceLive,
        bool _updateCache,
        bool _skipRQReentrantCheck
    ) internal returns (uint256 _utilization) {
        // console.log("_getUtilizationInternal: PART 1");
        EtherRouter.CachedConsEFxBalances memory _cachedBals = etherRouter.getConsolidatedEthFrxEthBalance(
            _forceLive,
            _updateCache
        );
        // console.log("_getUtilizationInternal: PART 2");
        return _getUtilizationPostCore(_cachedBals, _skipRQReentrantCheck);
    }

    /// @notice Return the current utilization. Calculates live AMO values
    /// @param _forceLive Force a live recalculation of the AMO values
    /// @param _updateCache Update the cached AMO values, if they were stale
    /// @return _utilization The current utilization
    /// @dev ETH in LP on the Curve AMO is considered "utilized" and thus a "liability"
    function getUtilization(bool _forceLive, bool _updateCache) public returns (uint256 _utilization) {
        return _getUtilizationInternal(_forceLive, _updateCache, false);
    }

    /// @notice Return the current utilization. Calculates live AMO values
    /// @return _utilization The current utilization
    /// @dev ETH in LP on the Curve AMO is considered "utilized" and thus a "liability"
    function getUtilizationView() public view returns (uint256 _utilization) {
        EtherRouter.CachedConsEFxBalances memory _cachedBals = etherRouter.getConsolidatedEthFrxEthBalanceView(true);
        return _getUtilizationPostCore(_cachedBals, false);
    }

    /// @notice Return the max amount of ETH available to borrow
    /// @return _maxBorrow The amount of ETH available to borrow
    function getMaxBorrow() external view returns (uint256 _maxBorrow) {
        EtherRouter.CachedConsEFxBalances memory _cachedBals = etherRouter.getConsolidatedEthFrxEthBalanceView(true);
        (, uint256 _rqShortage) = redemptionQueue.ethShortageOrSurplus();

        // If there is a shortage, you have to subtract it from the available borrow
        if (_cachedBals.ethTotalBalanced >= _rqShortage) {
            _maxBorrow = (_cachedBals.ethTotalBalanced - _rqShortage);
        } else {
            // _maxBorrow = 0; // Redundant set
        }
    }

    /// @notice Whether the provided validator pool is solvent, accounting just for accrued interest
    /// @param _validatorPoolAddress The validator pool address
    /// @return _isSolvent Whether the provided validator pool is solvent
    function isSolvent(address _validatorPoolAddress) public view returns (bool _isSolvent) {
        (_isSolvent, , ) = wouldBeSolvent(_validatorPoolAddress, true, 0, 0);
    }

    /// @notice Returns whether the public key has been approved by the beacon oracle
    /// @param _publicKey The pubkey to check
    /// @return _isApproved Whether the provided validator pool is solvent
    function isValidatorApproved(bytes calldata _publicKey) public view returns (bool _isApproved) {
        // Get the deposit info for the validator
        ValidatorDepositInfo memory _validatorDepositInfo = validatorDepositInfo[_publicKey];

        // Return early if it was never approved at all
        if (_validatorDepositInfo.whenValidatorApproved == 0) return false;

        // Fetch the validator pool info
        ValidatorPoolAccount memory _poolAcc = validatorPoolAccounts[_validatorDepositInfo.validatorPoolAddress];

        // A validator can only be approved if a withdrawal (if it ever happened in the first place)
        // occured before the beacon approval timestamp
        _isApproved = (_poolAcc.lastWithdrawal < _validatorDepositInfo.whenValidatorApproved);
    }

    /// @notice Convert borrow shares to Eth amount. Defaults to rounding up
    /// @param _shares Amount of borrow shares
    /// @return _borrowAmount The amount of Eth borrowed
    function toBorrowAmount(uint256 _shares) public view returns (uint256 _borrowAmount) {
        _borrowAmount = totalBorrow._toAmount(_shares, true);
    }

    /// @notice Convert borrow shares to Eth amount. Optionally rounds up
    /// @param _shares Amount of borrow shares
    /// @param _roundUp Amount of borrow shares
    /// @return _borrowAmount The amount of Eth borrowed
    function toBorrowAmountOptionalRoundUp(uint256 _shares, bool _roundUp) public view returns (uint256 _borrowAmount) {
        _borrowAmount = totalBorrow._toAmount(_shares, _roundUp);
    }

    /// @notice Helper method to check if the validator pool is/was liquidated
    /// @param _validatorPoolAddress The validator pool address
    /// @return _wasLiquidated Whether the validator pool is/was liquidated
    function wasLiquidated(address _validatorPoolAddress) public view returns (bool _wasLiquidated) {
        // Get the validator pool account info
        ValidatorPoolAccount memory _validatorPoolAccount = validatorPoolAccounts[_validatorPoolAddress];
        _wasLiquidated = _validatorPoolAccount.wasLiquidated;
    }

    /// @notice Solvency details for a validator pool, accounting for accrued interest.
    /// @param _validatorPoolAddress The validator pool address
    /// @param _accrueInterest Whether to accrue interest first. Should be true in most cases. False if you did it before somewhere and want to save gas
    /// @param _addlValidators Additional validators to test solvency for. Can be zero.
    /// @param _addlBorrowAmount Additional borrow amount to test solvency for. Can be zero.
    /// @return _wouldBeSolvent Whether the provided validator pool would be solvent given the interest accrual and additional borrow, if any.
    /// @return _borrowAmount Borrowed amount for the specified validator pool
    /// @return _creditAmount Credit amount for the specified validator pool
    function wouldBeSolvent(
        address _validatorPoolAddress,
        bool _accrueInterest,
        uint256 _addlValidators,
        uint256 _addlBorrowAmount
    ) public view returns (bool _wouldBeSolvent, uint256 _borrowAmount, uint256 _creditAmount) {
        // Get the validator pool account info
        ValidatorPoolAccount memory _validatorPoolAccount = validatorPoolAccounts[_validatorPoolAddress];

        // Accrue interest (non-write) first
        // Normally true, but false if you already did it previously in the same call and want to save gas
        VaultAccount memory _totalBorrow;
        if (_accrueInterest) {
            (, , , , _totalBorrow) = previewAddInterest();
        } else {
            _totalBorrow = totalBorrow;
        }

        // Get the borrowed amount for the validator pool, adding the new borrow amount if applicable
        _borrowAmount = _addlBorrowAmount + _totalBorrow._toAmount(_validatorPoolAccount.borrowShares, true);

        // Get the credit amount for the validator pool
        _creditAmount =
            _validatorPoolAccount.creditPerValidatorI48_E12 *
            MISSING_CREDPERVAL_MULT *
            (_validatorPoolAccount.validatorCount + _addlValidators);

        // Check if it is solvent, or if it was liquidated
        if ((_creditAmount >= _borrowAmount) && !_validatorPoolAccount.wasLiquidated) _wouldBeSolvent = true;
    }

    // ============================================================================================
    // Functions: Interest Accumulation and Adjustment
    // ============================================================================================

    /// @notice The ```AddInterest``` event is emitted when interest is accrued by borrowers
    /// @param interestEarned The total interest accrued by all borrowers
    /// @param rate The interest rate used to calculate accrued interest
    /// @param feesAmount The amount of fees paid to protocol
    /// @param feesShare The amount of shares distributed to protocol
    event AddInterest(uint256 interestEarned, uint256 rate, uint256 feesAmount, uint256 feesShare);

    /// @notice The ```UpdateRate``` event is emitted when the interest rate is updated
    /// @param oldRatePerSec The old interest rate (per second)
    /// @param oldFullUtilizationRate The old full utilization rate
    /// @param newRatePerSec The new interest rate (per second)
    /// @param newFullUtilizationRate The new full utilization rate
    event UpdateRate(
        uint256 oldRatePerSec,
        uint256 oldFullUtilizationRate,
        uint256 newRatePerSec,
        uint256 newFullUtilizationRate
    );

    /// @notice The ```addInterest``` function is a public implementation of _addInterest and allows 3rd parties to trigger interest accrual
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _feesAmount The amount of fees paid to protocol
    /// @return _feesShare The amount of shares distributed to protocol
    /// @return _currentRateInfo The new rate info struct
    /// @return _totalBorrow The new total borrow struct
    function addInterest(
        bool _returnAccounting
    )
        public
        nonReentrant
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo,
            VaultAccount memory _totalBorrow
        )
    {
        // Accrue interest
        (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest(false);

        // Optionally return borrow information
        if (_returnAccounting) {
            _totalBorrow = totalBorrow;
        }
    }

    /// @notice Same as addInterest but without the reentrancy check (it would be done on the calling function). Only EtherRouter or RedemptionQueue can call
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _feesAmount The amount of fees paid to protocol
    /// @return _feesShare The amount of shares distributed to protocol
    /// @return _currentRateInfo The new rate info struct
    /// @return _totalBorrow The new total borrow struct
    function addInterestPrivileged(
        bool _returnAccounting
    )
        external
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo,
            VaultAccount memory _totalBorrow
        )
    {
        // Skip reentrancy check for certain callers
        _requireSenderIsEtherRouterOrRedemptionQueue();

        // Accrue interest
        (, _interestEarned, _feesAmount, _feesShare, _currentRateInfo) = _addInterest(true);

        // Optionally return borrow information
        if (_returnAccounting) {
            _totalBorrow = totalBorrow;
        }
    }

    /// @notice Preview adding interest
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _feesAmount The amount of fees paid to protocol
    /// @return _feesShare The amount of shares distributed to protocol
    /// @return _newCurrentRateInfo The new rate info struct
    /// @return _totalBorrow The new total borrow struct
    function previewAddInterest()
        public
        view
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _newCurrentRateInfo,
            VaultAccount memory _totalBorrow
        )
    {
        _newCurrentRateInfo = currentRateInfo;

        // Write return values
        // InterestCalculationResults memory _results = _calculateInterestView(_newCurrentRateInfo);
        InterestCalculationResults memory _results = _calculateInterestWithStored(_newCurrentRateInfo);

        if (_results.isInterestUpdated) {
            _interestEarned = _results.interestEarned;

            _newCurrentRateInfo.ratePerSec = _results.newRate;
            _newCurrentRateInfo.fullUtilizationRate = _results.newFullUtilizationRate;

            _totalBorrow = _results.totalBorrow;
        } else {
            _totalBorrow = totalBorrow;
        }
    }

    struct InterestCalculationResults {
        bool isInterestUpdated;
        uint64 newRate;
        uint64 newFullUtilizationRate;
        uint256 interestEarned;
        VaultAccount totalBorrow;
    }

    /// @notice Calculates the interest to be accrued and the new interest rate info
    /// @param _currentRateInfo The current rate info
    /// @return _results The results of the interest calculation
    function _calculateInterestCore(
        CurrentRateInfo memory _currentRateInfo,
        uint256 _utilizationRate
    ) internal view returns (InterestCalculationResults memory _results) {
        // Short circuit if interest already calculated this block OR if interest is paused
        if (_currentRateInfo.lastTimestamp != block.timestamp) {
            // Indicate that interest is updated and calculated
            _results.isInterestUpdated = true;

            // Write return values and use these to save gas
            _results.totalBorrow = totalBorrow;

            // Time elapsed since last interest update
            uint256 _deltaTime = block.timestamp - _currentRateInfo.lastTimestamp;

            // Request new interest rate and full utilization rate from the rate calculator
            (_results.newRate, _results.newFullUtilizationRate) = rateCalculator.getNewRate(
                _deltaTime,
                _utilizationRate,
                _currentRateInfo.fullUtilizationRate
            );

            // Calculate interest accrued
            _results.interestEarned =
                (_deltaTime * _results.totalBorrow.amount * _results.newRate) /
                INTEREST_RATE_PRECISION;

            // Accrue interest (if any) and fees iff no overflow
            if (
                _results.interestEarned > 0 &&
                _results.interestEarned + _results.totalBorrow.amount <= type(uint128).max
            ) {
                // Increment totalBorrow by interestEarned
                _results.totalBorrow.amount += (_results.interestEarned).toUint128();
            }
        }
    }

    /// @notice Calculates the interest to be accrued and the new interest rate info. May update cached getConsolidatedEthFrxEthBalance values if stale
    /// @param _currentRateInfo The current rate info
    /// @return _results The results of the interest calculation
    function _calculateInterestWithStored(
        CurrentRateInfo memory _currentRateInfo
    ) internal view returns (InterestCalculationResults memory _results) {
        // // Get the potentially mutated utilization rate
        // uint256 _utilizationRate = getUtilization({ _forceLive: false, _updateCache: true });

        // Calculate the interest using the stored utilization rate
        return _calculateInterestCore(_currentRateInfo, utilizationStored);
    }

    // /// @notice Calculates the interest to be accrued and the new interest rate info. Will not update cached getConsolidatedEthFrxEthBalance values if stale
    // /// @param _currentRateInfo The current rate info
    // /// @return _results The results of the interest calculation
    // function _calculateInterestLiveView(
    //     CurrentRateInfo memory _currentRateInfo
    // ) internal view returns (InterestCalculationResults memory _results) {
    //     // Get the live utilization rate
    //     uint256 _utilizationRate = getUtilization({ _forceLive: true, _updateCache: false });

    //     // Calculate the interest
    //     return _calculateInterestCore(_currentRateInfo, _utilizationRate);
    // }

    /// @notice The ```_addInterest``` function is invoked prior to every external function and is used to accrue interest and update interest rate
    /// @dev Can only called once per block
    /// @param _skipRQReentrantCheck True to disable checking RedemptionQueue reentrancy. Only should be True for addInterestPrivileged calls
    /// @return _isInterestUpdated True if interest was calculated
    /// @return _interestEarned The amount of interest accrued by all borrowers
    /// @return _feesAmount The amount of fees paid to protocol
    /// @return _feesShare The amount of shares distributed to protocol
    /// @return _currentRateInfo The new rate info struct
    function _addInterest(
        bool _skipRQReentrantCheck
    )
        internal
        returns (
            bool _isInterestUpdated,
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo
        )
    {
        // Pull from storage and set default return values
        _currentRateInfo = currentRateInfo;

        // console.log("ADD INTEREST: PART 1");

        // Calc interest
        InterestCalculationResults memory _results = _calculateInterestWithStored(_currentRateInfo);

        // console.log("ADD INTEREST: PART 2");

        // Write return values only if interest was updated and calculated
        if (_results.isInterestUpdated) {
            // console.log("ADD INTEREST: PART 3");
            _isInterestUpdated = _results.isInterestUpdated;
            _interestEarned = _results.interestEarned;

            // Emit here so that we have access to the old values
            emit UpdateRate(
                _currentRateInfo.ratePerSec,
                _currentRateInfo.fullUtilizationRate,
                _results.newRate,
                _results.newFullUtilizationRate
            );
            emit AddInterest(_interestEarned, _results.newRate, _feesAmount, _feesShare);

            // Overwrite original values
            _currentRateInfo.ratePerSec = _results.newRate;
            _currentRateInfo.fullUtilizationRate = _results.newFullUtilizationRate;
            _currentRateInfo.lastTimestamp = uint64(block.timestamp);

            // console.log("ADD INTEREST: PART 4");

            // Effects: write to state
            currentRateInfo = _currentRateInfo;
            totalBorrow = _results.totalBorrow;
            interestAccrued += _interestEarned;
        }

        // Update the utilization
        utilizationStored = _getUtilizationInternal(true, true, _skipRQReentrantCheck);

        // console.log("ADD INTEREST: PART 5");
    }

    /// @notice Updates the utilizationStored
    function updateUtilization() public {
        utilizationStored = _getUtilizationInternal(true, true, true);
    }

    // ==============================================================================
    // Repay Functions
    // ==============================================================================

    /// @notice When a repayment is made for a validator pool
    /// @param _payorAddress The address paying, usually the validator pool
    /// @param _targetPoolAddress The validator pool getting repaid
    /// @param _repayAmount Amount of Eth being repaid
    event Repay(address indexed _payorAddress, address _targetPoolAddress, uint256 _repayAmount);

    /// @notice Repay a given validator pool with the provided msg.value Eth. Anyone can call and pay off on behalf of another.
    /// @param _targetPool The validator pool getting repaid
    function repay(address _targetPool) external payable nonReentrant {
        // Make sure the validator pool is initialized
        _requireValidatorPoolInitialized(_targetPool);

        // Accrue interest first
        _addInterest(false);

        // Do repay accounting for the target validator pool
        _repay(_targetPool, msg.value);

        // Give the repaid Ether to the Ether Router for investing
        etherRouter.depositEther{ value: msg.value }();

        // Update the stored utilization rate
        updateUtilization();
    }

    /// @notice Repay a given validator pool
    /// @param _targetPoolAddress The validator pool getting repaid
    /// @param _repayAmount Amount of Eth being repaid
    function _repay(address _targetPoolAddress, uint256 _repayAmount) internal {
        // Calculations
        (ValidatorPoolAccount memory _validatorPoolAccount, VaultAccount memory _totalBorrow) = _previewRepay(
            _targetPoolAddress,
            _repayAmount
        );

        // Effects
        validatorPoolAccounts[_targetPoolAddress] = _validatorPoolAccount;
        totalBorrow = _totalBorrow;

        emit Repay(msg.sender, _targetPoolAddress, _repayAmount);
    }

    /// @notice Preview repaying a validator pool
    /// @param _targetPoolAddress The validator pool getting repaid
    /// @param _repayAmount Amount of Eth being repaid
    /// @return _newValidatorPoolAccount The new state of the pool after the repayment
    /// @return _newTotalBorrow The new total amount of borrowed Eth after the repayment
    function _previewRepay(
        address _targetPoolAddress,
        uint256 _repayAmount
    )
        internal
        view
        returns (ValidatorPoolAccount memory _newValidatorPoolAccount, VaultAccount memory _newTotalBorrow)
    {
        // Copy dont mutate

        _newValidatorPoolAccount = validatorPoolAccounts[_targetPoolAddress];
        _newTotalBorrow = totalBorrow;

        // Calculate repaid share
        uint256 _sharesToRepay = _newTotalBorrow._toShares(_repayAmount, false);

        // Set values
        if (_sharesToRepay > _newValidatorPoolAccount.borrowShares) revert RepayingTooMuch();
        _newValidatorPoolAccount.borrowShares -= _sharesToRepay; // <<< HERE
        _newTotalBorrow.shares -= _sharesToRepay;
        _newTotalBorrow.amount -= _repayAmount;
    }

    // ==============================================================================
    // Borrow Functions
    // ==============================================================================

    /// @notice When the validator pool borrows from the lending pool
    /// @param _validatorPool The validator pool whose borrowing credit will be used
    /// @param _recipient The recipient of the Eth.
    /// @param _borrowAmount Amount of Eth being borrowed
    event Borrow(address indexed _validatorPool, address _recipient, uint256 _borrowAmount);

    /// @notice Borrow Eth from the lending pool (callable by a validator pool only)
    /// @param _recipient The recipient of the Eth
    /// @param _borrowAmount Amount of Eth being borrowed
    /// @dev The Eth is sourced from the EtherRouter
    function borrow(address payable _recipient, uint256 _borrowAmount) external nonReentrant {
        // Make sure the validator pool is initialized
        _requireValidatorPoolInitialized(msg.sender);

        // Accrue interest first
        _addInterest(false);

        // Do borrow accounting for the validator
        _borrow(msg.sender, _recipient, _borrowAmount, _borrowAmount);

        // Make sure the validator is still solvent after doing the accounting
        _requireValidatorPoolIsSolvent(msg.sender);

        // Pull Eth from the Ether Router and give it to the recipient (not necessarily the validator pool)
        etherRouter.requestEther(_recipient, _borrowAmount, false);

        // Update the stored utilization rate
        updateUtilization();
    }

    /// @notice Borrow Eth (internal)
    /// @param _validatorPoolAddress The validator pool address
    /// @param _recipient The recipient of the Eth
    /// @param _borrowAmount Amount of Eth being borrowed
    /// @param _allowanceAmount Validator pool's borrowing allowance
    function _borrow(
        address _validatorPoolAddress,
        address _recipient,
        uint256 _borrowAmount,
        uint256 _allowanceAmount
    ) internal {
        // Make sure the minimum borrow amount is met
        if (_borrowAmount < MINIMUM_BORROW_AMOUNT) revert MinimumBorrowAmount();

        // Calculations
        (ValidatorPoolAccount memory _validatorPoolAccount, VaultAccount memory _totalBorrow) = _previewBorrow(
            _validatorPoolAddress,
            _borrowAmount,
            _allowanceAmount
        );

        // Effects
        validatorPoolAccounts[_validatorPoolAddress] = _validatorPoolAccount;
        totalBorrow = _totalBorrow;

        // Make sure the validator is still solvent after doing the accounting
        // SKIPPED HERE as finalDepositValidator() would revert. Checked after the fact in external borrow()

        // Make sure the validator has not been liquidated
        _requireValidatorPoolNotLiquidated(msg.sender);

        emit Borrow(_validatorPoolAddress, _recipient, _borrowAmount);
    }

    /// @notice Preview borrowing some Eth
    /// @param _validatorPoolAddress The validator pool doing the borrowing
    /// @param _borrowAmount Amount of Eth being borrowed
    /// @return _newValidatorPoolAccount The new state of the pool after the borrow
    /// @return _newTotalBorrow The new total amount of borrowed Eth after the borrow
    function _previewBorrow(
        address _validatorPoolAddress,
        uint256 _borrowAmount,
        uint256 _allowanceAmount
    )
        internal
        view
        returns (ValidatorPoolAccount memory _newValidatorPoolAccount, VaultAccount memory _newTotalBorrow)
    {
        // Copy dont mutate
        _newValidatorPoolAccount = validatorPoolAccounts[_validatorPoolAddress];
        _newTotalBorrow = totalBorrow;

        // Set return values
        _newValidatorPoolAccount.borrowShares += _newTotalBorrow._toShares(_borrowAmount, true);
        if (_allowanceAmount.toUint128() > _newValidatorPoolAccount.borrowAllowance) revert AllowanceWouldBeNegative();
        else _newValidatorPoolAccount.borrowAllowance -= _allowanceAmount.toUint128();
        _newTotalBorrow.shares += _newTotalBorrow._toShares(_borrowAmount, true);
        _newTotalBorrow.amount += _borrowAmount;
    }

    // ==============================================================================
    // Deposit Functions
    // ==============================================================================

    /// @notice When a validator pool initially deposits
    /// @param _validatorPoolAddress Address of the validator pool
    /// @param _validatorPublicKey The public key of the validator
    /// @param _depositAmount The deposit amount of the validator
    event InitialDeposit(
        address payable indexed _validatorPoolAddress,
        bytes _validatorPublicKey,
        uint256 _depositAmount
    );

    /// @notice When a validator pool finalizes a deposit
    /// @param _validatorPoolAddress Address of the validator pool
    /// @param _validatorPublicKey The public key of the validator
    /// @param _poolSuppliedAmount The amount the validator pool supplied
    /// @param _borrowedAmount The amount borrowed in order to complete the deposit
    event DepositFinalized(
        address payable indexed _validatorPoolAddress,
        bytes _validatorPublicKey,
        uint256 _poolSuppliedAmount,
        uint96 _borrowedAmount
    );

    /// @notice Perform accounting for the first deposit for a given validator. May be either partial or full
    /// @param _validatorPublicKey Public key of the validator
    /// @param _depositAmount Amount being deposited
    function initialDepositValidator(bytes calldata _validatorPublicKey, uint256 _depositAmount) external nonReentrant {
        // Make sure the validator pool is initialized
        _requireValidatorPoolInitialized(msg.sender);

        // Accrue interest beforehand
        _addInterest(false);

        // Fetch the deposit info
        ValidatorDepositInfo storage _depositInfo = validatorDepositInfo[_validatorPublicKey];

        // Make sure the pubkey isn't already complete/finalized
        if (_depositInfo.wasFullDepositOrFinalized) revert PubKeyAlreadyFinalized();

        // Make sure the pubkey is either associated with the msg.sender validator pool, or not associated at all
        // Helps against validators altering data for other pubkeys as well as front-running
        if (!(_depositInfo.validatorPoolAddress == msg.sender || _depositInfo.validatorPoolAddress == address(0))) {
            revert ValidatorPoolKeyMismatch();
        }

        // Liquidated validator pools need to be emptied and abandoned, and should not be able to add any new validators
        // If you are mid-way through a partial deposit and liquidation happens, you will need to manually complete the 32 ETH
        // with an EOA or something, then exit
        _requireValidatorPoolNotLiquidated(msg.sender);

        // Update individual validator accounting
        _depositInfo.userDepositedEther += uint96(_depositAmount);

        // (Special case) If this came in as a full 32 Eth deposit all at once, or a final partial deposit with no borrow,
        // mark it as complete.
        if (_depositInfo.userDepositedEther == 32 ether) {
            // Verify that adding 1 validator would keep the validator pool solvent
            // You already accrued interest above so can leave false to save gas
            // _addlBorrowAmount is 0 since you came in full 32 all at once
            // Does not actually write these changes, just checks
            {
                (bool _wouldBeSolvent, uint256 _ttlBorrow, uint256 _ttlCredit) = wouldBeSolvent(
                    msg.sender,
                    false,
                    1,
                    0
                );
                if (!_wouldBeSolvent) revert ValidatorPoolIsNotSolventDetailed(_ttlBorrow, _ttlCredit);
            }

            // Mark the deposit as finalized
            _depositInfo.wasFullDepositOrFinalized = true;
        }

        // Mark the sender as the first validator so front-running attempts to alter the withdrawal address
        // will revert
        _depositInfo.validatorPoolAddress = msg.sender;

        // Make sure you are not depositing more than 32 Eth for this pubkey
        if (_depositInfo.userDepositedEther > 32 ether) revert CannotDepositMoreThan32Eth();

        // Update the stored utilization rate
        updateUtilization();

        emit InitialDeposit(payable(msg.sender), _validatorPublicKey, _depositAmount);
    }

    /// @notice Finalizes an incomplete ETH2 deposit made earlier, borrowing any remainder from the lending pool
    /// @param _validatorPublicKey Public key of the validator
    /// @param _withdrawalCredentials Withdrawal credentials for the validator
    /// @param _validatorSignature Signature from the validator
    /// @param _depositDataRoot Part of the deposit message
    function finalDepositValidator(
        bytes calldata _validatorPublicKey,
        bytes calldata _withdrawalCredentials,
        bytes calldata _validatorSignature,
        bytes32 _depositDataRoot
    ) external nonReentrant {
        _requireValidatorInitialized(_validatorPublicKey);
        _requireValidatorApproved(_validatorPublicKey);
        _requireValidatorPoolInitialized(msg.sender);
        _requireAddressAssociatedWithPubkey(msg.sender, _validatorPublicKey);

        // Fetch the deposit info
        ValidatorDepositInfo memory _depositInfo = validatorDepositInfo[_validatorPublicKey];

        // Make sure the pubkey wasn't used yet
        if (_depositInfo.wasFullDepositOrFinalized) revert PubKeyAlreadyFinalized();

        // Calculate the borrow amount and make sure it is nonzero
        uint96 _borrowAmount = 32 ether - _depositInfo.userDepositedEther;
        if (_borrowAmount == 0) revert NoDepositToFinalize();

        // Update the deposit info
        _depositInfo.lendingPoolDepositedEther += _borrowAmount;
        _depositInfo.wasFullDepositOrFinalized = true;
        validatorDepositInfo[_validatorPublicKey] = _depositInfo;

        // Accrue interest beforehand
        _addInterest(false);

        // You can borrow even if you don't have credit, assuming you can still pay the interest rate
        // Your partial deposit (at least 8 ETH here for anon due to 24 ETH credit),
        // plus the fact that exited ETH is trapped in the validator pool (until debts are paid),
        // is essentially the collateral
        _borrow({
            _validatorPoolAddress: msg.sender,
            _recipient: msg.sender,
            _borrowAmount: uint256(_borrowAmount),
            _allowanceAmount: 0
        });

        // Request the needed Eth
        etherRouter.requestEther(payable(address(this)), uint256(_borrowAmount), false);

        // Complete the deposit
        ETH2_DEPOSIT_CONTRACT.deposit{ value: uint256(_borrowAmount) }(
            _validatorPublicKey,
            _withdrawalCredentials,
            _validatorSignature,
            _depositDataRoot
        );

        // Verify that accruing interest and adding 1 validator would keep the validator pool solvent
        // You already accrued interest above so can leave false to save gas
        // BorrowAmount already increased by _borrowAmount so no need to put it in wouldBeSolvent()
        // Does not actually write these changes, just checks
        {
            (bool _wouldBeSolvent, uint256 _ttlBorrow, uint256 _ttlCredit) = wouldBeSolvent(msg.sender, false, 1, 0);
            if (!_wouldBeSolvent) revert ValidatorPoolIsNotSolventDetailed(_ttlBorrow, _ttlCredit);
        }

        // // Increment the validator count, but NOT the borrow allowance. This prevents immediate liquidation.
        // // TODO: Check to make sure this cannot be manipulated to never allow a liquidation.
        // validatorPoolAccounts[msg.sender].validatorCount++;

        // Update the utilization
        updateUtilization();

        emit DepositFinalized(payable(msg.sender), _validatorPublicKey, _depositInfo.userDepositedEther, _borrowAmount);
    }

    // ==============================================================================
    // Withdraw Functions
    // ==============================================================================

    /// @notice When a validator pool withdraws ETH
    /// @param _validatorPoolAddress Address of the validator pool
    /// @param _endRecipient The ultimate recipient of the ETH
    /// @param _sentBackAmount Amount of Eth actually given back (requested - fee)
    /// @param _feeAmount Amount of Eth kept as the withdrawal fee (sent to the Ether Router)
    event WithdrawalRegistered(
        address payable indexed _validatorPoolAddress,
        address payable _endRecipient,
        uint256 _sentBackAmount,
        uint256 _feeAmount
    );

    /// @notice Registers that a validator pool is withdrawing and resets the borrowAllowance to 0 until the next beacon update.
    /// @param _endRecipient The ultimate recipient of the Eth. msg.sender (the validator pool) should get any ETH first
    /// @param _sentBackAmount Amount of Eth actually given back (requested - fee)
    /// @param _feeAmount Amount of Eth kept as the withdrawal fee (sent to the Ether Router)
    /// @dev This prevents syncing issues between when the ETH comes back from a Beacon Chain exit (dumped into the validator pool)
    /// and letting the validator pool borrow "for free" with lesser collateral (since there was just an exit)
    /// Once the Beacon Oracle actually registers the beacon chain exit, borrowAllowance
    /// will simply be (# validators) * (credit per validator) and the validator pool can borrow normally again
    /// with the new, correct number of total validators
    function registerWithdrawal(
        address payable _endRecipient,
        uint256 _sentBackAmount,
        uint256 _feeAmount
    ) external nonReentrant {
        _requireValidatorPoolInitialized(msg.sender);

        // Catch up the interest
        _addInterest(false);

        // Fetch the validator pool info
        ValidatorPoolAccount memory _validatorPoolAccount = validatorPoolAccounts[msg.sender];

        // Make sure debts have been paid off first
        if (_validatorPoolAccount.borrowShares == 0) {
            // 0 balance turns off borrowing until next oracle update, to prevent front running
            _validatorPoolAccount.borrowAllowance = 0;
        } else {
            revert BorrowBalanceMustBeZero();
        }

        // Mark this withdrawal timestamp. Important to prevent beacon frontrunning and other attacks
        _validatorPoolAccount.lastWithdrawal = uint32(block.timestamp);

        // Update the validator pool struct
        validatorPoolAccounts[msg.sender] = _validatorPoolAccount;

        // Update the utilization
        updateUtilization();

        emit WithdrawalRegistered(payable(msg.sender), _endRecipient, _sentBackAmount, _feeAmount);
    }

    // ==============================================================================
    // Liquidate Functions
    // ==============================================================================

    /// @notice When a validator pool is liquidated
    /// @param _validatorPoolAddress Address of the validator pool
    /// @param _amountToLiquidate Amount of Eth to liquidate
    event Liquidate(address indexed _validatorPoolAddress, uint256 _amountToLiquidate);

    /// @notice Liquidate a specified amount of Eth for a validator pool. Callable only by an allowed liquidator, the timelock, or the beacon oracle
    /// @param _validatorPoolAddress Address of the validator pool
    /// @param _amountToLiquidate Amount of Eth to liquidate
    /// @dev Marks the pool as "wasLiquidated = true", which will prevent new borrows and deposits
    function liquidate(address payable _validatorPoolAddress, uint256 _amountToLiquidate) external payable {
        // Make sure the caller is allowed
        _requireAddressCanLiquidate(msg.sender);

        // Accrue interest
        _addInterest(false);

        // Don't liquidate if the position is healthy
        if (isSolvent(_validatorPoolAddress)) {
            revert ValidatorPoolIsSolvent();
        }

        // Mark the validator pool as being in liquidation
        validatorPoolAccounts[_validatorPoolAddress].wasLiquidated = true;

        // Force the validator pool to pay back its loan
        ValidatorPool(_validatorPoolAddress).repayWithPoolAndValue{ value: 0 }(_amountToLiquidate);

        emit Liquidate(_validatorPoolAddress, _amountToLiquidate);
    }

    // ==============================================================================
    // ETH Handling
    // ==============================================================================

    /// @notice Allows contract to receive Eth
    receive() external payable {
        // Do nothing except take in the Eth
    }

    /// @notice When the lending pool sends stranded ETH to the Ether Router
    /// @param _amountRecovered Amount of ETH recovered
    event StrandedEthRecovered(uint256 _amountRecovered);

    /// @notice Pushes ETH back into the Ether Router, in case ETH gets stuck in this contract somehow
    /// @dev Under normal operations, ETH is only in this contract transiently.
    function recoverStrandedEth() external returns (uint256 _amountRecovered) {
        _requireSenderIsTimelock();

        // Save the balance before
        _amountRecovered = address(this).balance;

        // Give the ETH to the Ether Router
        (bool _success, ) = address(etherRouter).call{ value: _amountRecovered }("");
        require(_success, "ETH transfer failed (recoverStrandedEth ETH)");

        emit StrandedEthRecovered(_amountRecovered);
    }

    // ==============================================================================
    // Restricted Functions
    // ==============================================================================

    /// @notice When the interest rate calculator is set
    /// @param addr Address being set
    event InterestRateCalculatorSet(address addr);

    /// @notice Set the address for the interest rate calculator
    /// @param _calculatorAddress Address to set
    function setInterestRateCalculator(address _calculatorAddress) external {
        _requireSenderIsTimelock();

        // Set the status
        rateCalculator = IInterestRateCalculator(_calculatorAddress);

        emit InterestRateCalculatorSet(_calculatorAddress);
    }

    /// @notice When an address is allowed/disallowed to liquidate
    /// @param addr Address being set
    /// @param canLiquidate Whether it can liquidate or not
    event LiquidatorSet(address addr, bool canLiquidate);

    /// @notice Allow/disallow an address to perform liquidations
    /// @param _liquidatorAddress Address to set
    /// @param _canLiquidate Whether it can liquidate or not
    function setLiquidator(address _liquidatorAddress, bool _canLiquidate) external {
        _requireSenderIsTimelock();

        // Set the status
        isLiquidator[_liquidatorAddress] = _canLiquidate;

        emit LiquidatorSet(_liquidatorAddress, _canLiquidate);
    }

    /// @notice Change the Beacon Oracle address
    /// @param _newBeaconOracleAddress Beacon Oracle address
    function setBeaconOracleAddress(address _newBeaconOracleAddress) external {
        _requireSenderIsTimelock();
        _setBeaconOracle(_newBeaconOracleAddress);
    }

    /// @notice Change the Ether Router address
    /// @param _newEtherRouterAddress Ether Router address
    function setEtherRouterAddress(address payable _newEtherRouterAddress) external {
        _requireSenderIsTimelock();
        _setEtherRouter(_newEtherRouterAddress);
    }

    /// @notice Change the Redemption Queue address
    /// @param _newRedemptionQueue Redemption Queue address
    function setRedemptionQueueAddress(address payable _newRedemptionQueue) external {
        _requireSenderIsTimelock();
        _setFraxEtherRedemptionQueueV2(_newRedemptionQueue);
    }

    // ==============================================================================
    // Errors
    // ==============================================================================
    /// @notice If the borrow allowance trying to be set would be negative
    error AllowanceWouldBeNegative();

    /// @notice Cannot withdraw with nonzero borrow balance
    error BorrowBalanceMustBeZero();

    /// @notice Cannot exit pool
    error CannotExitPool();

    /// @notice When you are trying to deposit more than 32 ETH
    error CannotDepositMoreThan32Eth();

    /// @notice When certain supplied arrays parameters have differing lengths
    error InputArrayLengthMismatch();

    /// @notice Invalid validator pool
    error InvalidValidatorPool();

    /// @notice If you are trying to finalize an already completed deposit
    error NoDepositToFinalize();

    /// @notice If the caller is not allowed to liquidate
    error NotAllowedLiquidator();

    /// @notice If the sender is not the EtherRouter or RedemptionQueue
    error NotEtherRouterOrRedemptionQueue();

    /// @notice When you are trying to borrow less than the minimum amount
    error MinimumBorrowAmount();

    /// @notice Must repay debt first
    error MustRepayDebtFirst();

    /// @notice Prevent trying to cycle pubkeys and get more debt
    error PubKeyAlreadyFinalized();

    /// @notice When have a reentrant call
    error ReentrancyStatusIsTrue();

    /// @notice When you try to repay too much
    error RepayingTooMuch();

    /// @notice Validator is not approved
    error ValidatorIsNotApprovedLP();

    /// @notice Validator is not initialized
    error ValidatorIsNotInitialized();

    /// @notice Supplied pubkey not associated with the supplied validator pool address
    error ValidatorPoolKeyMismatch();

    /// @notice Validator pool is liquidated
    error ValidatorPoolWasLiquidated();

    /// @notice Validator pool is not solvent
    error ValidatorPoolIsNotSolvent();

    /// @notice Validator pool is not solvent (detailed)
    error ValidatorPoolIsNotSolventDetailed(uint256 _ttlBorrow, uint256 _ttlCredit);

    /// @notice Validator pool is solvent
    error ValidatorPoolIsSolvent();

    /// @notice Withdrawal timestamp mismatch
    error WithdrawalTimestampMismatch(uint32 _suppliedTimestamp, uint32 _actualTimestamp);
}

File 22 of 44 : SSTORE2.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
    uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called.

    /*//////////////////////////////////////////////////////////////
                               WRITE LOGIC
    //////////////////////////////////////////////////////////////*/

    function write(bytes memory data) internal returns (address pointer) {
        // Prefix the bytecode with a STOP opcode to ensure it cannot be called.
        bytes memory runtimeCode = abi.encodePacked(hex"00", data);

        bytes memory creationCode = abi.encodePacked(
            //---------------------------------------------------------------------------------------------------------------//
            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
            //---------------------------------------------------------------------------------------------------------------//
            // 0x60    |  0x600B             | PUSH1 11     | codeOffset                                                     //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
            // 0xf3    |  0xf3               | RETURN       |                                                                //
            //---------------------------------------------------------------------------------------------------------------//
            hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes.
            runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit.
        );

        /// @solidity memory-safe-assembly
        assembly {
            // Deploy a new contract with the generated creation code.
            // We start 32 bytes into the code to avoid copying the byte length.
            pointer := create(0, add(creationCode, 32), mload(creationCode))
        }

        require(pointer != address(0), "DEPLOYMENT_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                               READ LOGIC
    //////////////////////////////////////////////////////////////*/

    function read(address pointer) internal view returns (bytes memory) {
        return readBytecode(pointer, DATA_OFFSET, pointer.code.length - DATA_OFFSET);
    }

    function read(address pointer, uint256 start) internal view returns (bytes memory) {
        start += DATA_OFFSET;

        return readBytecode(pointer, start, pointer.code.length - start);
    }

    function read(
        address pointer,
        uint256 start,
        uint256 end
    ) internal view returns (bytes memory) {
        start += DATA_OFFSET;
        end += DATA_OFFSET;

        require(pointer.code.length >= end, "OUT_OF_BOUNDS");

        return readBytecode(pointer, start, end - start);
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function readBytecode(
        address pointer,
        uint256 start,
        uint256 size
    ) private view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            data := mload(0x40)

            // Update the free memory pointer to prevent overriding our data.
            // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)).
            // Adding 31 to size and running the result through the logic above ensures
            // the memory pointer remains word-aligned, following the Solidity convention.
            mstore(0x40, add(data, and(add(add(size, 32), 31), not(31))))

            // Store the size of the data in the first 32 byte chunk of free memory.
            mstore(data, size)

            // Copy the code into memory right after the 32 bytes we used to store the size.
            extcodecopy(pointer, add(data, 32), start, size)
        }
    }
}

File 23 of 44 : ERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.20;

import {IERC721} from "./IERC721.sol";
import {IERC721Receiver} from "./IERC721Receiver.sol";
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {Strings} from "../../utils/Strings.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    mapping(uint256 tokenId => address) private _owners;

    mapping(address owner => uint256) private _balances;

    mapping(uint256 tokenId => address) private _tokenApprovals;

    mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual returns (uint256) {
        if (owner == address(0)) {
            revert ERC721InvalidOwner(address(0));
        }
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _requireOwned(tokenId);
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
        _requireOwned(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual {
        _approve(to, tokenId, _msgSender());
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual returns (address) {
        _requireOwned(tokenId);

        return _getApproved(tokenId);
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
        // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
        address previousOwner = _update(to, tokenId, _msgSender());
        if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
        transferFrom(from, to, tokenId);
        _checkOnERC721Received(from, to, tokenId, data);
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     *
     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
     * core ERC721 logic MUST be matched with the use of {_increaseBalance} to keep balances
     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
     */
    function _getApproved(uint256 tokenId) internal view virtual returns (address) {
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
     * particular (ignoring whether it is owned by `owner`).
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
        return
            spender != address(0) &&
            (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
    }

    /**
     * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
     * Reverts if `spender` does not have approval from the provided `owner` for the given token or for all its assets
     * the `spender` for the specific `tokenId`.
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
        if (!_isAuthorized(owner, spender, tokenId)) {
            if (owner == address(0)) {
                revert ERC721NonexistentToken(tokenId);
            } else {
                revert ERC721InsufficientApproval(spender, tokenId);
            }
        }
    }

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
     *
     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the
     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
     * remain consistent with one another.
     */
    function _increaseBalance(address account, uint128 value) internal virtual {
        unchecked {
            _balances[account] += value;
        }
    }

    /**
     * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that
     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).
     *
     * Emits a {Transfer} event.
     *
     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
        address from = _ownerOf(tokenId);

        // Perform (optional) operator check
        if (auth != address(0)) {
            _checkAuthorized(from, auth, tokenId);
        }

        // Execute the update
        if (from != address(0)) {
            // Clear approval. No need to re-authorize or emit the Approval event
            _approve(address(0), tokenId, address(0), false);

            unchecked {
                _balances[from] -= 1;
            }
        }

        if (to != address(0)) {
            unchecked {
                _balances[to] += 1;
            }
        }

        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        return from;
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner != address(0)) {
            revert ERC721InvalidSender(address(0));
        }
    }

    /**
     * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        _checkOnERC721Received(address(0), to, tokenId, data);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        address previousOwner = _update(address(0), tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        } else if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
     * are aware of the ERC721 standard to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is like {safeTransferFrom} in the sense that it invokes
     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `tokenId` token must exist and be owned by `from`.
     * - `to` cannot be the zero address.
     * - `from` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId) internal {
        _safeTransfer(from, to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        _checkOnERC721Received(from, to, tokenId, data);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
     * either the owner of the token, or approved to operate on all tokens held by this owner.
     *
     * Emits an {Approval} event.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address to, uint256 tokenId, address auth) internal {
        _approve(to, tokenId, auth, true);
    }

    /**
     * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
     * emitted in the context of transfers.
     */
    function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
        // Avoid reading the owner unless necessary
        if (emitEvent || auth != address(0)) {
            address owner = _requireOwned(tokenId);

            // We do not use _isAuthorized because single-token approvals should not be able to call approve
            if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
                revert ERC721InvalidApprover(auth);
            }

            if (emitEvent) {
                emit Approval(owner, to, tokenId);
            }
        }

        _tokenApprovals[tokenId] = to;
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Requirements:
     * - operator can't be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        if (operator == address(0)) {
            revert ERC721InvalidOperator(operator);
        }
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
     * Returns the owner.
     *
     * Overrides to ownership logic should be done to {_ownerOf}.
     */
    function _requireOwned(uint256 tokenId) internal view returns (address) {
        address owner = _ownerOf(tokenId);
        if (owner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
        return owner;
    }

    /**
     * @dev Private function to invoke {IERC721Receiver-onERC721Received} on a target address. This will revert if the
     * recipient doesn't accept the token transfer. The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     */
    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private {
        if (to.code.length > 0) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                if (retval != IERC721Receiver.onERC721Received.selector) {
                    revert ERC721InvalidReceiver(to);
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert ERC721InvalidReceiver(to);
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }
}

File 24 of 44 : SafeCast.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.

pragma solidity ^0.8.20;

/**
 * @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.
 */
library SafeCast {
    /**
     * @dev Value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);

    /**
     * @dev An int value doesn't fit in an uint of `bits` size.
     */
    error SafeCastOverflowedIntToUint(int256 value);

    /**
     * @dev Value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);

    /**
     * @dev An uint value doesn't fit in an int of `bits` size.
     */
    error SafeCastOverflowedUintToInt(uint256 value);

    /**
     * @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
     */
    function toUint248(uint256 value) internal pure returns (uint248) {
        if (value > type(uint248).max) {
            revert SafeCastOverflowedUintDowncast(248, value);
        }
        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
     */
    function toUint240(uint256 value) internal pure returns (uint240) {
        if (value > type(uint240).max) {
            revert SafeCastOverflowedUintDowncast(240, value);
        }
        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
     */
    function toUint232(uint256 value) internal pure returns (uint232) {
        if (value > type(uint232).max) {
            revert SafeCastOverflowedUintDowncast(232, value);
        }
        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
     */
    function toUint224(uint256 value) internal pure returns (uint224) {
        if (value > type(uint224).max) {
            revert SafeCastOverflowedUintDowncast(224, value);
        }
        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
     */
    function toUint216(uint256 value) internal pure returns (uint216) {
        if (value > type(uint216).max) {
            revert SafeCastOverflowedUintDowncast(216, value);
        }
        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
     */
    function toUint208(uint256 value) internal pure returns (uint208) {
        if (value > type(uint208).max) {
            revert SafeCastOverflowedUintDowncast(208, value);
        }
        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
     */
    function toUint200(uint256 value) internal pure returns (uint200) {
        if (value > type(uint200).max) {
            revert SafeCastOverflowedUintDowncast(200, value);
        }
        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
     */
    function toUint192(uint256 value) internal pure returns (uint192) {
        if (value > type(uint192).max) {
            revert SafeCastOverflowedUintDowncast(192, value);
        }
        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
     */
    function toUint184(uint256 value) internal pure returns (uint184) {
        if (value > type(uint184).max) {
            revert SafeCastOverflowedUintDowncast(184, value);
        }
        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
     */
    function toUint176(uint256 value) internal pure returns (uint176) {
        if (value > type(uint176).max) {
            revert SafeCastOverflowedUintDowncast(176, value);
        }
        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
     */
    function toUint168(uint256 value) internal pure returns (uint168) {
        if (value > type(uint168).max) {
            revert SafeCastOverflowedUintDowncast(168, value);
        }
        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
     */
    function toUint160(uint256 value) internal pure returns (uint160) {
        if (value > type(uint160).max) {
            revert SafeCastOverflowedUintDowncast(160, value);
        }
        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
     */
    function toUint152(uint256 value) internal pure returns (uint152) {
        if (value > type(uint152).max) {
            revert SafeCastOverflowedUintDowncast(152, value);
        }
        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
     */
    function toUint144(uint256 value) internal pure returns (uint144) {
        if (value > type(uint144).max) {
            revert SafeCastOverflowedUintDowncast(144, value);
        }
        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
     */
    function toUint136(uint256 value) internal pure returns (uint136) {
        if (value > type(uint136).max) {
            revert SafeCastOverflowedUintDowncast(136, value);
        }
        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
     */
    function toUint128(uint256 value) internal pure returns (uint128) {
        if (value > type(uint128).max) {
            revert SafeCastOverflowedUintDowncast(128, value);
        }
        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
     */
    function toUint120(uint256 value) internal pure returns (uint120) {
        if (value > type(uint120).max) {
            revert SafeCastOverflowedUintDowncast(120, value);
        }
        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
     */
    function toUint112(uint256 value) internal pure returns (uint112) {
        if (value > type(uint112).max) {
            revert SafeCastOverflowedUintDowncast(112, value);
        }
        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
     */
    function toUint104(uint256 value) internal pure returns (uint104) {
        if (value > type(uint104).max) {
            revert SafeCastOverflowedUintDowncast(104, value);
        }
        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
     */
    function toUint96(uint256 value) internal pure returns (uint96) {
        if (value > type(uint96).max) {
            revert SafeCastOverflowedUintDowncast(96, value);
        }
        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
     */
    function toUint88(uint256 value) internal pure returns (uint88) {
        if (value > type(uint88).max) {
            revert SafeCastOverflowedUintDowncast(88, value);
        }
        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
     */
    function toUint80(uint256 value) internal pure returns (uint80) {
        if (value > type(uint80).max) {
            revert SafeCastOverflowedUintDowncast(80, value);
        }
        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
     */
    function toUint72(uint256 value) internal pure returns (uint72) {
        if (value > type(uint72).max) {
            revert SafeCastOverflowedUintDowncast(72, value);
        }
        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
     */
    function toUint64(uint256 value) internal pure returns (uint64) {
        if (value > type(uint64).max) {
            revert SafeCastOverflowedUintDowncast(64, value);
        }
        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
     */
    function toUint56(uint256 value) internal pure returns (uint56) {
        if (value > type(uint56).max) {
            revert SafeCastOverflowedUintDowncast(56, value);
        }
        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
     */
    function toUint48(uint256 value) internal pure returns (uint48) {
        if (value > type(uint48).max) {
            revert SafeCastOverflowedUintDowncast(48, value);
        }
        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
     */
    function toUint40(uint256 value) internal pure returns (uint40) {
        if (value > type(uint40).max) {
            revert SafeCastOverflowedUintDowncast(40, value);
        }
        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
     */
    function toUint32(uint256 value) internal pure returns (uint32) {
        if (value > type(uint32).max) {
            revert SafeCastOverflowedUintDowncast(32, value);
        }
        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
     */
    function toUint24(uint256 value) internal pure returns (uint24) {
        if (value > type(uint24).max) {
            revert SafeCastOverflowedUintDowncast(24, value);
        }
        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
     */
    function toUint16(uint256 value) internal pure returns (uint16) {
        if (value > type(uint16).max) {
            revert SafeCastOverflowedUintDowncast(16, value);
        }
        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
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        if (value > type(uint8).max) {
            revert SafeCastOverflowedUintDowncast(8, value);
        }
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        if (value < 0) {
            revert SafeCastOverflowedIntToUint(value);
        }
        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
     */
    function toInt248(int256 value) internal pure returns (int248 downcasted) {
        downcasted = int248(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(248, value);
        }
    }

    /**
     * @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
     */
    function toInt240(int256 value) internal pure returns (int240 downcasted) {
        downcasted = int240(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(240, value);
        }
    }

    /**
     * @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
     */
    function toInt232(int256 value) internal pure returns (int232 downcasted) {
        downcasted = int232(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(232, value);
        }
    }

    /**
     * @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
     */
    function toInt224(int256 value) internal pure returns (int224 downcasted) {
        downcasted = int224(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(224, value);
        }
    }

    /**
     * @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
     */
    function toInt216(int256 value) internal pure returns (int216 downcasted) {
        downcasted = int216(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(216, value);
        }
    }

    /**
     * @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
     */
    function toInt208(int256 value) internal pure returns (int208 downcasted) {
        downcasted = int208(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(208, value);
        }
    }

    /**
     * @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
     */
    function toInt200(int256 value) internal pure returns (int200 downcasted) {
        downcasted = int200(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(200, value);
        }
    }

    /**
     * @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
     */
    function toInt192(int256 value) internal pure returns (int192 downcasted) {
        downcasted = int192(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(192, value);
        }
    }

    /**
     * @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
     */
    function toInt184(int256 value) internal pure returns (int184 downcasted) {
        downcasted = int184(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(184, value);
        }
    }

    /**
     * @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
     */
    function toInt176(int256 value) internal pure returns (int176 downcasted) {
        downcasted = int176(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(176, value);
        }
    }

    /**
     * @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
     */
    function toInt168(int256 value) internal pure returns (int168 downcasted) {
        downcasted = int168(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(168, value);
        }
    }

    /**
     * @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
     */
    function toInt160(int256 value) internal pure returns (int160 downcasted) {
        downcasted = int160(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(160, value);
        }
    }

    /**
     * @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
     */
    function toInt152(int256 value) internal pure returns (int152 downcasted) {
        downcasted = int152(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(152, value);
        }
    }

    /**
     * @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
     */
    function toInt144(int256 value) internal pure returns (int144 downcasted) {
        downcasted = int144(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(144, value);
        }
    }

    /**
     * @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
     */
    function toInt136(int256 value) internal pure returns (int136 downcasted) {
        downcasted = int136(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(136, value);
        }
    }

    /**
     * @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
     */
    function toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(128, value);
        }
    }

    /**
     * @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
     */
    function toInt120(int256 value) internal pure returns (int120 downcasted) {
        downcasted = int120(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(120, value);
        }
    }

    /**
     * @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
     */
    function toInt112(int256 value) internal pure returns (int112 downcasted) {
        downcasted = int112(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(112, value);
        }
    }

    /**
     * @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
     */
    function toInt104(int256 value) internal pure returns (int104 downcasted) {
        downcasted = int104(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(104, value);
        }
    }

    /**
     * @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
     */
    function toInt96(int256 value) internal pure returns (int96 downcasted) {
        downcasted = int96(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(96, value);
        }
    }

    /**
     * @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
     */
    function toInt88(int256 value) internal pure returns (int88 downcasted) {
        downcasted = int88(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(88, value);
        }
    }

    /**
     * @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
     */
    function toInt80(int256 value) internal pure returns (int80 downcasted) {
        downcasted = int80(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(80, value);
        }
    }

    /**
     * @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
     */
    function toInt72(int256 value) internal pure returns (int72 downcasted) {
        downcasted = int72(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(72, value);
        }
    }

    /**
     * @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
     */
    function toInt64(int256 value) internal pure returns (int64 downcasted) {
        downcasted = int64(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(64, value);
        }
    }

    /**
     * @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
     */
    function toInt56(int256 value) internal pure returns (int56 downcasted) {
        downcasted = int56(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(56, value);
        }
    }

    /**
     * @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
     */
    function toInt48(int256 value) internal pure returns (int48 downcasted) {
        downcasted = int48(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(48, value);
        }
    }

    /**
     * @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
     */
    function toInt40(int256 value) internal pure returns (int40 downcasted) {
        downcasted = int40(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(40, value);
        }
    }

    /**
     * @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
     */
    function toInt32(int256 value) internal pure returns (int32 downcasted) {
        downcasted = int32(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(32, value);
        }
    }

    /**
     * @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
     */
    function toInt24(int256 value) internal pure returns (int24 downcasted) {
        downcasted = int24(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(24, value);
        }
    }

    /**
     * @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
     */
    function toInt16(int256 value) internal pure returns (int16 downcasted) {
        downcasted = int16(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(16, value);
        }
    }

    /**
     * @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
     */
    function toInt8(int256 value) internal pure returns (int8 downcasted) {
        downcasted = int8(value);
        if (downcasted != value) {
            revert SafeCastOverflowedIntDowncast(8, value);
        }
    }

    /**
     * @dev Converts an unsigned uint256 into a signed int256.
     *
     * Requirements:
     *
     * - input must be less than or equal to maxInt256.
     */
    function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        if (value > uint256(type(int256).max)) {
            revert SafeCastOverflowedUintToInt(value);
        }
        return int256(value);
    }
}

File 25 of 44 : IFrxEth.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;

interface IFrxEth {
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function acceptOwnership() external;

    function addMinter(address minter_address) external;

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

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

    function burn(uint256 amount) external;

    function burnFrom(address account, uint256 amount) external;

    function decimals() external view returns (uint8);

    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);

    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);

    function minter_burn_from(address b_address, uint256 b_amount) external;

    function minter_mint(address m_address, uint256 m_amount) external;

    function minters(address) external view returns (bool);

    function minters_array(uint256) external view returns (address);

    function name() external view returns (string memory);

    function nominateNewOwner(address _owner) external;

    function nominatedOwner() external view returns (address);

    function nonces(address owner) external view returns (uint256);

    function owner() external view returns (address);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function removeMinter(address minter_address) external;

    function setTimelock(address _timelock_address) external;

    function symbol() external view returns (string memory);

    function timelock_address() external view returns (address);

    function totalSupply() external view returns (uint256);

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

    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

File 26 of 44 : ISfrxEth.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;

interface ISfrxEth {
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function allowance(address, address) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function asset() external view returns (address);

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

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

    function convertToShares(uint256 assets) external view returns (uint256);

    function decimals() external view returns (uint8);

    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    function depositWithSignature(
        uint256 assets,
        address receiver,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 shares);

    function lastRewardAmount() external view returns (uint192);

    function lastSync() external view returns (uint32);

    function maxDeposit(address) external view returns (uint256);

    function maxMint(address) external view returns (uint256);

    function maxRedeem(address owner) external view returns (uint256);

    function maxWithdraw(address owner) external view returns (uint256);

    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    function name() external view returns (string memory);

    function nonces(address) external view returns (uint256);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function previewDeposit(uint256 assets) external view returns (uint256);

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

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

    function previewWithdraw(uint256 assets) external view returns (uint256);

    function pricePerShare() external view returns (uint256);

    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    function rewardsCycleEnd() external view returns (uint32);

    function rewardsCycleLength() external view returns (uint32);

    function symbol() external view returns (string memory);

    function syncRewards() external;

    function totalAssets() external view returns (uint256);

    function totalSupply() external view returns (uint256);

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

    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
}

File 27 of 44 : Ownable2Step.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./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.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. 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();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

File 28 of 44 : ILendingPool.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// interface ILendingPool {
//     event AddInterest(uint256 interestEarned, uint256 rate, uint256 feesAmount, uint256 feesShare);
//     event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
//     event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
//     event Erc20Recovered(address token, uint256 amount);
//     event EtherRecovered(uint256 amount);
//     event FeesCollected(address _recipient, uint96 _collectAmt);
//     event OperatorTransferred(address indexed previousOperator, address indexed newOperator);
//     event RedemptionQueueEntered(
//         address redeemer,
//         uint256 nftId,
//         uint256 amount,
//         uint32 maturityTimestamp,
//         uint96 redemptionFeeAmount
//     );
//     event RedemptionTicketNftRedeemed(address sender, address recipient, uint256 nftId, uint96 amountOut);
//     event SetBeaconOracle(address indexed oldBeaconOracle, address indexed newBeaconOracle);
//     event SetEtherRouter(address indexed oldEtherRouter, address indexed newEtherRouter);
//     event SetMaxOperatorQueueLength(uint32 _newMaxQueueLength);
//     event SetQueueLength(uint32 _newLength);
//     event SetRedemptionFee(uint32 _newFee);
//     event TimelockTransferStarted(address indexed previousTimelock, address indexed newTimelock);
//     event TimelockTransferred(address indexed previousTimelock, address indexed newTimelock);
//     event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
//     event UpdateRate(
//         uint256 oldRatePerSec,
//         uint256 oldFullUtilizationRate,
//         uint256 newRatePerSec,
//         uint256 newFullUtilizationRate
//     );

//     struct CurrentRateInfo {
//         uint64 lastTimestamp;
//         uint64 ratePerSec;
//         uint64 fullUtilizationRate;
//     }

//     struct VaultAccount {
//         uint256 amount;
//         uint256 shares;
//     }

//     function DEFAULT_CREDIT_PER_VALIDATOR_I48_E12() external view returns (uint256);

//     function ETH2_DEPOSIT_CONTRACT() external view returns (address);

//     function FEE_PRECISION() external view returns (uint32);

//     function INTEREST_RATE_PRECISION() external view returns (uint256);

//     function UTILIZATION_PRECISION() external view returns (uint256);

//     function acceptTransferTimelock() external;

//     function addInterest(
//         bool _returnAccounting
//     )
//         external
//         returns (
//             uint256 _interestEarned,
//             uint256 _feesAmount,
//             uint256 _feesShare,
//             CurrentRateInfo memory _currentRateInfo,
//             VaultAccount memory _totalBorrow
//         );

//     function approve(address to, uint256 tokenId) external;

//     function approveValidator(bytes memory _validatorPublicKey) external;

//     function balanceOf(address owner) external view returns (uint256);

//     function beaconOracle() external view returns (address);

//     function borrow(address _recipient, uint256 _borrowAmount) external;

//     function collectRedemptionFees(address _recipient, uint96 _collectAmt) external;

//     function currentRateInfo()
//         external
//         view
//         returns (uint64 lastTimestamp, uint64 ratePerSec, uint64 fullUtilizationRate);

//     function deployValidatorPool(address _validatorPoolOwnerAddress) external returns (address _pairAddress);

//     function enterRedemptionQueue(address _recipient, uint96 _amountToRedeem) external;

//     function enterRedemptionQueueWithPermit(
//         uint96 _amountToRedeem,
//         address _recipient,
//         uint256 _deadline,
//         uint8 _v,
//         bytes32 _r,
//         bytes32 _s
//     ) external;

//     function etherRouter() external view returns (address);

//     function finalDepositValidator(
//         bytes memory _validatorPublicKey,
//         bytes memory _withdrawalCredentials,
//         bytes memory _validatorSignature,
//         bytes32 _depositDataRoot
//     ) external;

//     function frxEth() external view returns (address);

//     function getApproved(uint256 tokenId) external view returns (address);

//     function getUtilization() external view returns (uint256 _utilization);

//     function initialDepositValidator(bytes memory _validatorPublicKey, uint256 _depositAmount) external;

//     function interestAccrued() external view returns (uint256);

//     function interestAvailableForWithdrawal() external view returns (uint256);

//     function rateCalculator() external view returns (address);

//     function isApprovedForAll(address owner, address operator) external view returns (bool);

//     function isSolvent(address _validatorPool) external view returns (bool _isSolvent);

//     function liquidate(address _validatorPoolAddress, uint256 _amountToLiquidate) external;

//     function maxOperatorQueueLength() external view returns (uint32);

//     function name() external view returns (string memory);

//     function nftInformation(uint256 nftId) external view returns (bool hasBeenRedeemed, uint32 maturity, uint96 amount);

//     function operatorAddress() external view returns (address);

//     function ownerOf(uint256 tokenId) external view returns (address);

//     function pendingTimelockAddress() external view returns (address);

//     function previewAddInterest()
//         external
//         view
//         returns (
//             uint256 _interestEarned,
//             uint256 _feesAmount,
//             uint256 _feesShare,
//             CurrentRateInfo memory _newCurrentRateInfo,
//             VaultAccount memory _totalBorrow
//         );

//     function recoverErc20(address _tokenAddress, uint256 _tokenAmount) external;

//     function recoverEther(uint256 amount) external;

//     function redeemRedemptionTicketNft(uint256 _nftId, address _recipient) external;

//     function redemptionQueueState() external view returns (uint32 nextNftId, uint32 queueLength, uint32 redemptionFee);

//     function renounceTimelock() external;

//     function repay(address _targetPool) external payable;

//     function safeTransferFrom(address from, address to, uint256 tokenId) external;

//     function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) external;

//     function setApprovalForAll(address operator, bool approved) external;

//     function setCreationCode(bytes memory _creationCode) external;

//     function setMaxOperatorQueueLength(uint32 _newMaxQueueLength) external;

//     function setOperator() external;

//     function setOperator(address _newOperator) external;

//     function setQueueLength(uint32 _newLength) external;

//     function setRedemptionFee(uint32 _newFee) external;

//     function setVPoolBorrowAllowance(address _validatorPoolAddress, uint128 _newBorrowAllowance) external;

//     function setVPoolCreditPerValidatorI48_E12(
//         address _validatorPoolAddress,
//         uint48 _newCreditPerValidatorI48_E12
//     ) external;

//     function setVPoolValidatorCount(address _validatorPoolAddress, uint32 _newValidatorCount) external;

//     function supportsInterface(bytes4 interfaceId) external view returns (bool);

//     function symbol() external view returns (string memory);

//     function timelockAddress() external view returns (address);

//     function toBorrowAmount(address _validatorPool, uint256 _shares) external view returns (uint256 _borrowAmount);

//     function tokenURI(uint256 tokenId) external view returns (string memory);

//     function totalBorrow() external view returns (uint256 amount, uint256 shares);

//     function transferFrom(address from, address to, uint256 tokenId) external;

//     function transferTimelock(address _newTimelock) external;

//     function validatorDepositInfo(
//         bytes memory _validatorPublicKey
//     ) external view returns (uint32 whenValidatorApproved, uint96 userDepositedEther, uint96 lendingPoolDepositedEther);

//     function validatorPoolAccounts(
//         address _validatorPool
//     )
//         external
//         view
//         returns (
//             bool isInitialized,
//             bool wasLiquidated,
//             uint32 lastWithdrawal,
//             uint32 validatorCount,
//             uint48 creditPerValidatorI48_E12,
//             uint128 borrowAllowance,
//             uint256 borrowShares
//         );

//     function validatorPoolCreationCodeAddress() external view returns (address);
// }
interface ILendingPool {
    struct CurrentRateInfo {
        uint64 lastTimestamp;
        uint64 ratePerSec;
        uint64 fullUtilizationRate;
    }

    struct VaultAccount {
        uint256 amount;
        uint256 shares;
    }

    function DEFAULT_CREDIT_PER_VALIDATOR_I48_E12() external view returns (uint48);
    function ETH2_DEPOSIT_CONTRACT() external view returns (address);
    function INTEREST_RATE_PRECISION() external view returns (uint256);
    function MAXIMUM_CREDIT_PER_VALIDATOR_I48_E12() external view returns (uint48);
    function MAX_WITHDRAWAL_FEE() external view returns (uint256);
    function MINIMUM_BORROW_AMOUNT() external view returns (uint256);
    function MISSING_CREDPERVAL_MULT() external view returns (uint256);
    function UTILIZATION_PRECISION() external view returns (uint256);
    function acceptTransferTimelock() external;
    function addInterest(
        bool _returnAccounting
    )
        external
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _currentRateInfo,
            VaultAccount memory _totalBorrow
        );
    function beaconOracle() external view returns (address);
    function borrow(address _recipient, uint256 _borrowAmount) external;
    function currentRateInfo()
        external
        view
        returns (uint64 lastTimestamp, uint64 ratePerSec, uint64 fullUtilizationRate);
    function deployValidatorPool(
        address _validatorPoolOwnerAddress,
        bytes32 _extraSalt
    ) external returns (address _poolAddress);
    function entrancyStatus() external view returns (bool _isEntered);
    function etherRouter() external view returns (address);
    function finalDepositValidator(
        bytes memory _validatorPublicKey,
        bytes memory _withdrawalCredentials,
        bytes memory _validatorSignature,
        bytes32 _depositDataRoot
    ) external;
    function frxETH() external view returns (address);
    function getLastWithdrawalTimestamp(
        address _validatorPoolAddress
    ) external returns (uint32 _lastWithdrawalTimestamp);
    function getLastWithdrawalTimestamps(
        address[] memory _validatorPoolAddresses
    ) external returns (uint32[] memory _lastWithdrawalTimestamps);
    function getMaxBorrow() external view returns (uint256 _maxBorrow);
    function getUtilization(bool _forceLive, bool _updateCache) external returns (uint256 _utilization);
    function getUtilizationView() external view returns (uint256 _utilization);
    function initialDepositValidator(bytes memory _validatorPublicKey, uint256 _depositAmount) external;
    function interestAccrued() external view returns (uint256);
    function isLiquidator(address _addr) external view returns (bool _canLiquidate);
    function isSolvent(address _validatorPoolAddress) external view returns (bool _isSolvent);
    function isValidatorApproved(bytes memory _publicKey) external view returns (bool _isApproved);
    function liquidate(address _validatorPoolAddress, uint256 _amountToLiquidate) external;
    function pendingTimelockAddress() external view returns (address);
    function previewAddInterest()
        external
        view
        returns (
            uint256 _interestEarned,
            uint256 _feesAmount,
            uint256 _feesShare,
            CurrentRateInfo memory _newCurrentRateInfo,
            VaultAccount memory _totalBorrow
        );
    function previewValidatorAccounts(address _validatorPoolAddress) external view returns (VaultAccount memory);
    function rateCalculator() external view returns (address);
    function recoverStrandedEth() external returns (uint256 _amountRecovered);
    function redemptionQueue() external view returns (address);
    function registerWithdrawal(address _endRecipient, uint256 _sentBackAmount, uint256 _feeAmount) external;
    function renounceTimelock() external;
    function repay(address _targetPool) external payable;
    function setBeaconOracleAddress(address _newBeaconOracleAddress) external;
    function setCreationCode(bytes memory _creationCode) external;
    function setEtherRouterAddress(address _newEtherRouterAddress) external;
    function setInterestRateCalculator(address _calculatorAddress) external;
    function setLiquidator(address _liquidatorAddress, bool _canLiquidate) external;
    function setRedemptionQueueAddress(address _newRedemptionQueue) external;
    function setVPoolCreditsPerValidator(
        address[] memory _validatorPoolAddresses,
        uint48[] memory _newCreditsPerValidator
    ) external;
    function setVPoolValidatorCountsAndBorrowAllowances(
        address[] memory _validatorPoolAddresses,
        bool _setValidatorCounts,
        bool _setBorrowAllowances,
        uint32[] memory _newValidatorCounts,
        uint128[] memory _newBorrowAllowances,
        uint32[] memory _lastWithdrawalTimestamps
    ) external;
    function setVPoolWithdrawalFee(uint256 _newFee) external;
    function setValidatorApprovals(
        bytes[] memory _validatorPublicKeys,
        address[] memory _validatorPoolAddresses,
        uint32[] memory _whenApprovedArr,
        uint32[] memory _lastWithdrawalTimestamps
    ) external;
    function timelockAddress() external view returns (address);
    function toBorrowAmount(uint256 _shares) external view returns (uint256 _borrowAmount);
    function toBorrowAmountOptionalRoundUp(
        uint256 _shares,
        bool _roundUp
    ) external view returns (uint256 _borrowAmount);
    function totalBorrow() external view returns (uint256 amount, uint256 shares);
    function transferTimelock(address _newTimelock) external;
    function updateUtilization() external;
    function utilizationStored() external view returns (uint256);
    function vPoolWithdrawalFee() external view returns (uint256);
    function validatorDepositInfo(
        bytes memory _validatorPublicKey
    )
        external
        view
        returns (
            uint32 whenValidatorApproved,
            bool wasFullDepositOrFinalized,
            address validatorPoolAddress,
            uint96 userDepositedEther,
            uint96 lendingPoolDepositedEther
        );
    function validatorPoolAccounts(
        address _validatorPool
    )
        external
        view
        returns (
            bool isInitialized,
            bool wasLiquidated,
            uint32 lastWithdrawal,
            uint32 validatorCount,
            uint48 creditPerValidatorI48_E12,
            uint128 borrowAllowance,
            uint256 borrowShares
        );
    function validatorPoolCreationCodeAddress() external view returns (address);
    function wasLiquidated(address _validatorPoolAddress) external view returns (bool _wasLiquidated);
    function wouldBeSolvent(
        address _validatorPoolAddress,
        bool _accrueInterest,
        uint256 _addlValidators,
        uint256 _addlBorrowAmount
    ) external view returns (bool _wouldBeSolvent, uint256 _borrowAmount, uint256 _creditAmount);
}

File 29 of 44 : IDepositContract.sol
// ┏━━━┓━┏┓━┏┓━━┏━━━┓━━┏━━━┓━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━┏┓━━━━━┏━━━┓━━━━━━━━━┏┓━━━━━━━━━━━━━━┏┓━
// ┃┏━━┛┏┛┗┓┃┃━━┃┏━┓┃━━┃┏━┓┃━━━━┗┓┏┓┃━━━━━━━━━━━━━━━━━━┏┛┗┓━━━━┃┏━┓┃━━━━━━━━┏┛┗┓━━━━━━━━━━━━┏┛┗┓
// ┃┗━━┓┗┓┏┛┃┗━┓┗┛┏┛┃━━┃┃━┃┃━━━━━┃┃┃┃┏━━┓┏━━┓┏━━┓┏━━┓┏┓┗┓┏┛━━━━┃┃━┗┛┏━━┓┏━┓━┗┓┏┛┏━┓┏━━┓━┏━━┓┗┓┏┛
// ┃┏━━┛━┃┃━┃┏┓┃┏━┛┏┛━━┃┃━┃┃━━━━━┃┃┃┃┃┏┓┃┃┏┓┃┃┏┓┃┃━━┫┣┫━┃┃━━━━━┃┃━┏┓┃┏┓┃┃┏┓┓━┃┃━┃┏┛┗━┓┃━┃┏━┛━┃┃━
// ┃┗━━┓━┃┗┓┃┃┃┃┃┃┗━┓┏┓┃┗━┛┃━━━━┏┛┗┛┃┃┃━┫┃┗┛┃┃┗┛┃┣━━┃┃┃━┃┗┓━━━━┃┗━┛┃┃┗┛┃┃┃┃┃━┃┗┓┃┃━┃┗┛┗┓┃┗━┓━┃┗┓
// ┗━━━┛━┗━┛┗┛┗┛┗━━━┛┗┛┗━━━┛━━━━┗━━━┛┗━━┛┃┏━┛┗━━┛┗━━┛┗┛━┗━┛━━━━┗━━━┛┗━━┛┗┛┗┛━┗━┛┗┛━┗━━━┛┗━━┛━┗━┛
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┃┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┗┛━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.23;

// 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 A processed deposit event.
    event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index);

    /// @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 root hash.
    /// @return The deposit root hash.
    function get_deposit_root() external view returns (bytes32);

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

// Based on official specification in https://eips.ethereum.org/EIPS/eip-165
interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceId The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceId` and
    ///  `interfaceId` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}

// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.
// It tries to stay as close as possible to the original source code.
/// @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
contract DepositContract is IDepositContract, ERC165 {
    uint256 constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
    // NOTE: this also ensures `deposit_count` will fit into 64-bits
    uint256 constant MAX_DEPOSIT_COUNT = 2 ** DEPOSIT_CONTRACT_TREE_DEPTH - 1;

    bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;
    uint256 deposit_count;

    bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;

    constructor() public {
        // Compute hashes in empty sparse Merkle tree
        for (uint256 height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++) {
            zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
        }
    }

    function get_deposit_root() external view override returns (bytes32) {
        bytes32 node;
        uint256 size = deposit_count;
        for (uint256 height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
            if ((size & 1) == 1) {
                node = sha256(abi.encodePacked(branch[height], node));
            } else {
                node = sha256(abi.encodePacked(node, zero_hashes[height]));
            }
            size /= 2;
        }
        return sha256(abi.encodePacked(node, to_little_endian_64(uint64(deposit_count)), bytes24(0)));
    }

    function get_deposit_count() external view override returns (bytes memory) {
        return to_little_endian_64(uint64(deposit_count));
    }

    function deposit(
        bytes calldata pubkey,
        bytes calldata withdrawal_credentials,
        bytes calldata signature,
        bytes32 deposit_data_root
    ) external payable override {
        // Extended ABI length checks since dynamic types are used.
        require(pubkey.length == 48, "DepositContract: invalid pubkey length");
        require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
        require(signature.length == 96, "DepositContract: invalid signature length");

        // Check deposit amount
        require(msg.value >= 1 ether, "DepositContract: deposit value too low");
        require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
        uint256 deposit_amount = msg.value / 1 gwei;
        require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");

        // Emit `DepositEvent` log
        bytes memory amount = to_little_endian_64(uint64(deposit_amount));
        emit DepositEvent(
            pubkey,
            withdrawal_credentials,
            amount,
            signature,
            to_little_endian_64(uint64(deposit_count))
        );

        // Compute deposit data root (`DepositData` hash tree root)
        bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0)));
        bytes32 signature_root = sha256(
            abi.encodePacked(
                sha256(abi.encodePacked(signature[:64])),
                sha256(abi.encodePacked(signature[64:], bytes32(0)))
            )
        );
        bytes32 node = sha256(
            abi.encodePacked(
                sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
                sha256(abi.encodePacked(amount, bytes24(0), signature_root))
            )
        );

        // Verify computed and expected deposit data roots match
        require(
            node == deposit_data_root,
            "DepositContract: reconstructed DepositData does not match supplied deposit_data_root"
        );

        // Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
        require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");

        // Add deposit data root to Merkle tree (update a single `branch` node)
        deposit_count += 1;
        uint256 size = deposit_count;
        for (uint256 height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
            if ((size & 1) == 1) {
                branch[height] = node;
                return;
            }
            node = sha256(abi.encodePacked(branch[height], node));
            size /= 2;
        }
        // As the loop should always end prematurely with the `return` statement,
        // this code should be unreachable. We assert `false` just to be safe.
        assert(false);
    }

    function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
        return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId;
    }

    function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
        ret = new bytes(8);
        bytes8 bytesValue = bytes8(value);
        // Byteswapping during copying to bytes.
        ret[0] = bytesValue[7];
        ret[1] = bytesValue[6];
        ret[2] = bytesValue[5];
        ret[3] = bytesValue[4];
        ret[4] = bytesValue[3];
        ret[5] = bytesValue[2];
        ret[6] = bytesValue[1];
        ret[7] = bytesValue[0];
    }
}

File 30 of 44 : VaultAccountingLibrary.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

struct VaultAccount {
    uint256 amount; // Total amount, analogous to market cap
    uint256 shares; // Total shares, analogous to shares outstanding
}

/// @title VaultAccount Library
/// @author Drake Evans (Frax Finance) github.com/drakeevans, modified from work by @Boring_Crypto github.com/boring_crypto
/// @notice Provides a library for use with the VaultAccount struct, provides convenient math implementations
/// @dev Uses uint128 to save on storage
library VaultAccountingLibrary {
    /// @notice Calculates the shares value in relationship to `amount` and `total`. Optionally rounds up.
    /// @dev Given an amount, return the appropriate number of shares
    function _toShares(
        VaultAccount memory _total,
        uint256 _amount,
        bool _roundUp
    ) internal pure returns (uint256 _shares) {
        if (_total.amount == 0) {
            _shares = _amount;
        } else {
            // May round down to 0 temporarily
            _shares = (_amount * _total.shares) / _total.amount;

            // Optionally round up to prevent certain attacks.
            if (_roundUp && (_shares * _total.amount < _amount * _total.shares)) {
                _shares = _shares + 1;
            }
        }
    }

    /// @notice Calculates the amount value in relationship to `shares` and `total`
    /// @dev Given a number of shares, returns the appropriate amount
    function _toAmount(
        VaultAccount memory _total,
        uint256 _shares,
        bool _roundUp
    ) internal pure returns (uint256 _amount) {
        // bool _roundUp = false;
        if (_total.shares == 0) {
            _amount = _shares;
        } else {
            // Rounds down for safety
            _amount = (_shares * _total.amount) / _total.shares;

            // Optionally round up to prevent certain attacks.
            if (_roundUp && (_amount * _total.shares < _shares * _total.amount)) {
                _amount = _amount + 1;
            }
        }
    }
}

File 31 of 44 : BeaconOracle.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// =========================== BeaconOracle ===========================
// ====================================================================
// Tracks frxETHV2 ValidatorPools. Controlled by Frax governance / bots

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans
// Travis Moore: https://github.com/FortisFortuna

// Reviewer(s) / Contributor(s)
// Dennis: https://github.com/denett
// Sam Kazemian: https://github.com/samkazemian

import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { ValidatorPool } from "./ValidatorPool.sol";
import { LendingPool } from "./lending-pool/LendingPool.sol";
import { LendingPoolRole } from "./access-control/LendingPoolRole.sol";
import { OperatorRole } from "frax-std/access-control/v2/OperatorRole.sol";

/// @title Tracks frxETHV2 ValidatorPools
/// @author Frax Finance
/// @notice Controlled by Frax governance / bots
contract BeaconOracle is LendingPoolRole, OperatorRole, Timelock2Step {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    /// @notice Constructor for the beacon oracle
    /// @param _timelockAddress The timelock address
    /// @param _operatorAddress The operator address
    constructor(
        address _timelockAddress,
        address _operatorAddress
    ) LendingPoolRole(payable(address(0))) OperatorRole(_operatorAddress) Timelock2Step(_timelockAddress) {}

    // ==============================================================================
    // Operator (and Timelock) Check Functions
    // ==============================================================================

    /// @notice Checks if msg.sender is the timelock address or the operator
    function _requireIsTimelockOrOperator() internal view {
        if (!((msg.sender == timelockAddress) || (msg.sender == operatorAddress))) revert NotTimelockOrOperator();
    }

    // ==============================================================================
    // Beacon Functions
    // ==============================================================================

    /// @notice Set the approval status for a single validator's pubkey
    /// @param _validatorPublicKey The pubkey being set
    /// @param _validatorPoolAddress The validator pool associated with the pubkey
    /// @param _whenApproved When the pubkey was approved. 0 if it is not
    /// @param _lastWithdrawalTimestamp Should be the timestamp of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setValidatorApproval(
        bytes calldata _validatorPublicKey,
        address _validatorPoolAddress,
        uint32 _whenApproved,
        uint32 _lastWithdrawalTimestamp
    ) external {
        _requireIsTimelockOrOperator();

        // Set arrays
        bytes[] memory tmpArr0 = new bytes[](1);
        tmpArr0[0] = _validatorPublicKey;
        address[] memory tmpArr1 = new address[](1);
        tmpArr1[0] = _validatorPoolAddress;
        uint32[] memory tmpArr2 = new uint32[](1);
        tmpArr2[0] = _whenApproved;
        uint32[] memory lwTimestampTmpArr = new uint32[](1);
        lwTimestampTmpArr[0] = _lastWithdrawalTimestamp;

        // Set the approvals
        lendingPool.setValidatorApprovals(tmpArr0, tmpArr1, tmpArr2, lwTimestampTmpArr);
    }

    /// @notice Set the approval status for a multiple validator pubkeys
    /// @param _validatorPublicKeys The pubkeys being set
    /// @param _validatorPoolAddresses The validator pools associated with the pubkeys
    /// @param _whenApprovedArr When the validators were approved. 0 if they were not
    /// @param _lastWithdrawalTimestamps Should be the timestamps of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setValidatorApprovals(
        bytes[] calldata _validatorPublicKeys,
        address[] calldata _validatorPoolAddresses,
        uint32[] calldata _whenApprovedArr,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireIsTimelockOrOperator();

        // Set the approvals
        lendingPool.setValidatorApprovals(
            _validatorPublicKeys,
            _validatorPoolAddresses,
            _whenApprovedArr,
            _lastWithdrawalTimestamps
        );
    }

    /// @notice Set the borrow allowance for a single validator pool
    /// @param _validatorPoolAddress The validator pool being set
    /// @param _newBorrowAllowance The new borrow allowance
    /// @param _lastWithdrawalTimestamp Should be the timestamp of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolBorrowAllowance(
        address _validatorPoolAddress,
        uint128 _newBorrowAllowance,
        uint32 _lastWithdrawalTimestamp
    ) external {
        _requireIsTimelockOrOperator();

        // Set arrays
        address[] memory vpAddrTmpArr = new address[](1);
        vpAddrTmpArr[0] = _validatorPoolAddress;
        uint128[] memory nbaTmpArr = new uint128[](1);
        nbaTmpArr[0] = _newBorrowAllowance;
        uint32[] memory emptyArr = new uint32[](0);
        uint32[] memory lwTimestampTmpArr = new uint32[](1);
        lwTimestampTmpArr[0] = _lastWithdrawalTimestamp;

        // Set the borrow allowance only, for a single validator pool
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            vpAddrTmpArr,
            false,
            true,
            emptyArr,
            nbaTmpArr,
            lwTimestampTmpArr
        );
    }

    /// @notice Set the borrow allowances for a multiple validator pools
    /// @param _validatorPoolAddresses The validator pools being set
    /// @param _newBorrowAllowances The new borrow allowances
    /// @param _lastWithdrawalTimestamps Should be the timestamps of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolBorrowAllowances(
        address[] calldata _validatorPoolAddresses,
        uint128[] calldata _newBorrowAllowances,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireIsTimelockOrOperator();
        uint32[] memory emptyArr = new uint32[](0);

        // Set the borrow allowances only, for a multiple validator pools
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            _validatorPoolAddresses,
            false,
            true,
            emptyArr,
            _newBorrowAllowances,
            _lastWithdrawalTimestamps
        );
    }

    /// @notice Set the credits per validator for a single validator pool
    /// @param _validatorPoolAddress The validator pool being set
    /// @param _newCreditPerValidatorI48_E12 The ETH credit per validator this pool should be given
    function setVPoolCreditPerValidatorI48_E12(
        address _validatorPoolAddress,
        uint48 _newCreditPerValidatorI48_E12
    ) external {
        _requireIsTimelockOrOperator();

        // Set arrays
        address[] memory vpAddrTmpArr = new address[](1);
        vpAddrTmpArr[0] = _validatorPoolAddress;
        uint48[] memory ncpvTmpArr = new uint48[](1);
        ncpvTmpArr[0] = _newCreditPerValidatorI48_E12;

        lendingPool.setVPoolCreditsPerValidator(vpAddrTmpArr, ncpvTmpArr);
    }

    /// @notice Set the credits per validator for a multiple validator pools
    /// @param _validatorPoolAddresses The validator pools being set
    /// @param _newCreditsPerValidator The ETH credits per validator each pool should be given
    function setVPoolCreditsPerValidator(
        address[] calldata _validatorPoolAddresses,
        uint48[] calldata _newCreditsPerValidator
    ) external {
        _requireIsTimelockOrOperator();
        lendingPool.setVPoolCreditsPerValidator(_validatorPoolAddresses, _newCreditsPerValidator);
    }

    /// @notice Set the number of validators for a single validator pool
    /// @param _validatorPoolAddress The validator pool being set
    /// @param _newValidatorCount The new total number of validators for the pool
    /// @param _lastWithdrawalTimestamp Should be the timestamp of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolValidatorCount(
        address _validatorPoolAddress,
        uint32 _newValidatorCount,
        uint32 _lastWithdrawalTimestamp
    ) external {
        _requireIsTimelockOrOperator();

        // Set arrays
        address[] memory vpAddrTmpArr = new address[](1);
        vpAddrTmpArr[0] = _validatorPoolAddress;
        uint32[] memory nvcTmpArr = new uint32[](1);
        nvcTmpArr[0] = _newValidatorCount;
        uint128[] memory emptyArr = new uint128[](0);
        uint32[] memory lwTimestampTmpArr = new uint32[](1);
        lwTimestampTmpArr[0] = _lastWithdrawalTimestamp;

        // Set the count only, for a single validator pool
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            vpAddrTmpArr,
            true,
            false,
            nvcTmpArr,
            emptyArr,
            lwTimestampTmpArr
        );
    }

    /// @notice Set the number of validators for multiple validator pools
    /// @param _validatorPoolAddresses The validator pools being set
    /// @param _newValidatorCounts The new total number of validators for the pools
    /// @param _lastWithdrawalTimestamps Should be the timestamps of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolValidatorCounts(
        address[] calldata _validatorPoolAddresses,
        uint32[] calldata _newValidatorCounts,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireIsTimelockOrOperator();
        uint128[] memory emptyArr = new uint128[](0);

        // Set the counts only, for multiple validator pools
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            _validatorPoolAddresses,
            true,
            false,
            _newValidatorCounts,
            emptyArr,
            _lastWithdrawalTimestamps
        );
    }

    /// @notice Set the number of validators and the borrow allowance for a single validator pools
    /// @param _validatorPoolAddress The validator pool being set
    /// @param _newValidatorCount The new total number of validators for the pool
    /// @param _newBorrowAllowance The new borrow allowance
    /// @param _lastWithdrawalTimestamp Should be the timestamp of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolValidatorCountAndBorrowAllowance(
        address _validatorPoolAddress,
        uint32 _newValidatorCount,
        uint128 _newBorrowAllowance,
        uint32 _lastWithdrawalTimestamp
    ) external {
        _requireIsTimelockOrOperator();

        // Set arrays
        address[] memory vpAddrTmpArr = new address[](1);
        vpAddrTmpArr[0] = _validatorPoolAddress;
        uint32[] memory nvcTmpArr = new uint32[](1);
        nvcTmpArr[0] = _newValidatorCount;
        uint128[] memory nbaTmpArr = new uint128[](1);
        nbaTmpArr[0] = _newBorrowAllowance;
        uint32[] memory lwTimestampTmpArr = new uint32[](1);
        lwTimestampTmpArr[0] = _lastWithdrawalTimestamp;

        // Set both the count and borrow allowance for a single validator pool
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            vpAddrTmpArr,
            true,
            true,
            nvcTmpArr,
            nbaTmpArr,
            lwTimestampTmpArr
        );
    }

    /// @notice Set the number of validators, as well as their allowances, for multiple validator pools
    /// @param _validatorPoolAddresses The validator pools being set
    /// @param _newValidatorCounts The new total number of validators for the pools
    /// @param _newBorrowAllowances The new borrow allowances
    /// @param _lastWithdrawalTimestamps Should be the timestamps of when the user last withdrew. Function will revert if user withdraws after this function is enqueued.
    function setVPoolValidatorCountsAndBorrowAllowances(
        address[] calldata _validatorPoolAddresses,
        uint32[] calldata _newValidatorCounts,
        uint128[] calldata _newBorrowAllowances,
        uint32[] calldata _lastWithdrawalTimestamps
    ) external {
        _requireIsTimelockOrOperator();

        // Set both the counts and borrow allowances for multiple validator pools
        lendingPool.setVPoolValidatorCountsAndBorrowAllowances(
            _validatorPoolAddresses,
            true,
            true,
            _newValidatorCounts,
            _newBorrowAllowances,
            _lastWithdrawalTimestamps
        );
    }

    // ==============================================================================
    // Restricted Functions
    // ==============================================================================

    /// @notice Set the lending pool address
    /// @param _newLendingPoolAddress The new address of the lending pool
    function setLendingPool(address payable _newLendingPoolAddress) external {
        _requireSenderIsTimelock();
        _setLendingPool(_newLendingPoolAddress);
    }

    /// @notice Change the Operator address
    /// @param _newOperatorAddress Operator address
    function setOperatorAddress(address _newOperatorAddress) external {
        _requireSenderIsTimelock();
        _setOperator(_newOperatorAddress);
    }

    // ====================================
    // Errors
    // ====================================

    /// @notice Thrown if the sender is not the timelock or the operator
    error NotTimelockOrOperator();
}

File 32 of 44 : BeaconOracleRole.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================= BeaconOracleRole =========================
// ====================================================================
// Access control for the Beacon Oracle

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans

// Reviewer(s) / Contributor(s)
// Travis Moore: https://github.com/FortisFortuna
// Dennis: https://github.com/denett

import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { BeaconOracle } from "../BeaconOracle.sol";

abstract contract BeaconOracleRole {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    BeaconOracle public beaconOracle;

    /// @notice constructor
    /// @param _beaconOracle Address of Beacon Oracle
    constructor(address _beaconOracle) {
        beaconOracle = BeaconOracle(_beaconOracle);
    }

    // ==============================================================================
    // Configuration Setters
    // ==============================================================================

    /// @notice Sets a new Beacon Oracle
    /// @param _beaconOracle Address for the new Beacon Oracle
    function _setBeaconOracle(address _beaconOracle) internal {
        emit SetBeaconOracle(address(beaconOracle), _beaconOracle);
        beaconOracle = BeaconOracle(_beaconOracle);
    }

    // ==============================================================================
    // Internal Checks
    // ==============================================================================

    /// @notice Checks if an address is the Beacon Oracle
    /// @param _address Address to test
    function _isBeaconOracle(address _address) internal view returns (bool) {
        return (_address == address(beaconOracle));
    }

    /// @notice Reverts if the address is not the Beacon Oracle
    /// @param _address Address to test
    function _requireIsBeaconOracle(address _address) internal view {
        if (!_isBeaconOracle(_address)) {
            revert AddressIsNotBeaconOracle(address(beaconOracle), _address);
        }
    }

    /// @notice Reverts if msg.sender is not the Beacon Oracle
    function _requireSenderIsBeaconOracle() internal view {
        _requireIsBeaconOracle(msg.sender);
    }

    // ==============================================================================
    // Events
    // ==============================================================================

    /// @notice The ```SetBeaconOracle``` event fires when the Beacon Oracle address changes
    /// @param oldBeaconOracle The old address
    /// @param newBeaconOracle The new address
    event SetBeaconOracle(address indexed oldBeaconOracle, address indexed newBeaconOracle);

    // ==============================================================================
    // Errors
    // ==============================================================================

    /// @notice Emitted when the test address is not the Beacon Oracle
    error AddressIsNotBeaconOracle(address beaconOracleAddress, address actualAddress);
}

File 33 of 44 : EtherRouterRole.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================== EtherRouterRole =========================
// ====================================================================
// Access control for the Ether Router

// Frax Finance: https://github.com/FraxFinance

// Primary Author(s)
// Drake Evans: https://github.com/DrakeEvans

// Reviewer(s) / Contributor(s)
// Travis Moore: https://github.com/FortisFortuna
// Dennis: https://github.com/denett

import { EtherRouter } from "../ether-router/EtherRouter.sol";

abstract contract EtherRouterRole {
    // ==============================================================================
    // Storage & Constructor
    // ==============================================================================

    EtherRouter public etherRouter;

    /// @notice constructor
    /// @param _etherRouter Address of Ether Router
    constructor(address payable _etherRouter) {
        etherRouter = EtherRouter(_etherRouter);
    }

    // ==============================================================================
    // Configuration Setters
    // ==============================================================================

    /// @notice Sets a new Ether Router
    /// @param _etherRouter Address for the new Ether Router.
    function _setEtherRouter(address payable _etherRouter) internal {
        emit SetEtherRouter(address(etherRouter), _etherRouter);
        etherRouter = EtherRouter(_etherRouter);
    }

    // ==============================================================================
    // Internal Checks
    // ==============================================================================

    /// @notice Checks if an address is the Ether Router
    /// @param _address Address to test
    function _isEtherRouter(address _address) internal view returns (bool) {
        return (_address == address(etherRouter));
    }

    /// @notice Reverts if the address is not the Ether Router
    /// @param _address Address to test
    function _requireIsEtherRouter(address _address) internal view {
        if (!_isEtherRouter(_address)) {
            revert AddressIsNotEtherRouter(address(etherRouter), _address);
        }
    }

    /// @notice Reverts if msg.sender is not the Ether Router
    function _requireSenderIsEtherRouter() internal view {
        _requireIsEtherRouter(msg.sender);
    }

    // ==============================================================================
    // Events
    // ==============================================================================

    /// @notice The ```SetEtherRouter``` event fires when the Ether Router address changes
    /// @param oldEtherRouter The old address
    /// @param newEtherRouter The new address
    event SetEtherRouter(address indexed oldEtherRouter, address indexed newEtherRouter);

    // ==============================================================================
    // Errors
    // ==============================================================================

    /// @notice Emitted when the test address is not the Ether Router
    error AddressIsNotEtherRouter(address etherRouterAddress, address actualAddress);
}

File 34 of 44 : IFrxEth.sol
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.23;

interface IFrxEth {
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function acceptOwnership() external;

    function addMinter(address minter_address) external;

    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

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

    function burn(uint256 amount) external;

    function burnFrom(address account, uint256 amount) external;

    function decimals() external view returns (uint8);

    function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);

    function increaseAllowance(address spender, uint256 addedValue) external returns (bool);

    function minter_burn_from(address b_address, uint256 b_amount) external;

    function minter_mint(address m_address, uint256 m_amount) external;

    function minters(address) external view returns (bool);

    function minters_array(uint256) external view returns (address);

    function name() external view returns (string memory);

    function nominateNewOwner(address _owner) external;

    function nominatedOwner() external view returns (address);

    function nonces(address owner) external view returns (uint256);

    function owner() external view returns (address);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function removeMinter(address minter_address) external;

    function setTimelock(address _timelock_address) external;

    function symbol() external view returns (string memory);

    function timelock_address() external view returns (address);

    function totalSupply() external view returns (uint256);

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

    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

File 35 of 44 : IInterestRateCalculator.sol
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

interface IInterestRateCalculator {
    function name() external view returns (string memory);

    function version() external view returns (uint256, uint256, uint256);

    function getNewRate(
        uint256 _deltaTime,
        uint256 _utilization,
        uint64 _maxInterest
    ) external view returns (uint64 _newRatePerSec, uint64 _newMaxInterest);
}

File 36 of 44 : IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

File 37 of 44 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.20;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be
     * reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

File 38 of 44 : IERC721Metadata.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.20;

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

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

File 39 of 44 : Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

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

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

    /**
     * @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), HEX_DIGITS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toStringSigned(int256 value) internal pure returns (string memory) {
        return string.concat(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) {
        uint256 localValue = value;
        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] = HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            revert StringsInsufficientHexLength(value, length);
        }
        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 bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

File 40 of 44 : ERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "./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);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

File 41 of 44 : Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @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 {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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 42 of 44 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

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

File 43 of 44 : Math.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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 towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (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 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                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.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

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

            uint256 twos = denominator & (0 - denominator);
            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 (unsignedRoundsUp(rounding) && 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
     * towards zero.
     *
     * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

File 44 of 44 : SignedMath.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

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

Settings
{
  "remappings": [
    "ds-test/=lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "frax-std/=node_modules/frax-standard-solidity/src/",
    "src/=src/",
    "frax-ether-redemption-queue/=lib/frax-ether-redemption-queue-dev/src/",
    "solmate/=node_modules/solmate/",
    "@chainlink/=node_modules/@chainlink/",
    "@openzeppelin/=node_modules/@openzeppelin/",
    "@eth-optimism/=node_modules/@eth-optimism/",
    "frax-ether-redemption-queue-dev/=lib/frax-ether-redemption-queue-dev/",
    "frax-standard-solidity/=node_modules/frax-standard-solidity/",
    "solidity-bytes-utils/=node_modules/solidity-bytes-utils/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 800
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": false,
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"_timelockAddress","type":"address"},{"internalType":"address","name":"_operatorAddress","type":"address"},{"internalType":"address","name":"_frxEthAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"redemptionQueueAddress","type":"address"},{"internalType":"address","name":"actualAddress","type":"address"}],"name":"AddressIsNotFraxEtherRedemptionQueueV2","type":"error"},{"inputs":[{"internalType":"address","name":"lendingPoolAddress","type":"address"},{"internalType":"address","name":"actualAddress","type":"address"}],"name":"AddressIsNotLendingPool","type":"error"},{"inputs":[{"internalType":"address","name":"operatorAddress","type":"address"},{"internalType":"address","name":"actualAddress","type":"address"}],"name":"AddressIsNotOperator","type":"error"},{"inputs":[{"internalType":"address","name":"pendingTimelockAddress","type":"address"},{"internalType":"address","name":"actualAddress","type":"address"}],"name":"AddressIsNotPendingTimelock","type":"error"},{"inputs":[{"internalType":"address","name":"timelockAddress","type":"address"},{"internalType":"address","name":"actualAddress","type":"address"}],"name":"AddressIsNotTimelock","type":"error"},{"inputs":[],"name":"AmoAlreadyExists","type":"error"},{"inputs":[],"name":"AmoAlreadyOffOrMissing","type":"error"},{"inputs":[{"internalType":"uint256","name":"step","type":"uint256"}],"name":"EthTransferFailedER","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InvalidAmo","type":"error"},{"inputs":[],"name":"InvalidRecoverEtherTransfer","type":"error"},{"inputs":[{"internalType":"uint256","name":"remainingEth","type":"uint256"}],"name":"NotEnoughEthPulled","type":"error"},{"inputs":[],"name":"NotLendingPoolOrRedemptionQueue","type":"error"},{"inputs":[],"name":"NotTimelockOrOperator","type":"error"},{"inputs":[],"name":"RedemptionQueueAddressAlreadySet","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"Erc20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EtherRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"requesterAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountToRequester","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountToRedemptionQueue","type":"uint256"}],"name":"EtherRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"destAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EtherSwept","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"amoAddress","type":"address"}],"name":"FrxEthAmoAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"amoAddress","type":"address"}],"name":"FrxEthAmoRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOperator","type":"address"},{"indexed":true,"internalType":"address","name":"newOperator","type":"address"}],"name":"OperatorTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"depositToAddress","type":"address"},{"indexed":false,"internalType":"address","name":"withdrawFromAddress","type":"address"}],"name":"PreferredDepositAndWithdrawalAmoAddressesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldFraxEtherRedemptionQueueV2","type":"address"},{"indexed":true,"internalType":"address","name":"newFraxEtherRedemptionQueueV2","type":"address"}],"name":"SetFraxEtherRedemptionQueueV2","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldLendingPool","type":"address"},{"indexed":true,"internalType":"address","name":"newLendingPool","type":"address"}],"name":"SetLendingPool","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousTimelock","type":"address"},{"indexed":true,"internalType":"address","name":"newTimelock","type":"address"}],"name":"TimelockTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousTimelock","type":"address"},{"indexed":true,"internalType":"address","name":"newTimelock","type":"address"}],"name":"TimelockTransferred","type":"event"},{"inputs":[],"name":"acceptTransferTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_amoAddress","type":"address"}],"name":"addAmo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"amos","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"amosArray","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"cachedConsEFxEBals","outputs":[{"internalType":"bool","name":"isStale","type":"bool"},{"internalType":"address","name":"amoAddress","type":"address"},{"internalType":"uint96","name":"ethFree","type":"uint96"},{"internalType":"uint96","name":"ethInLpBalanced","type":"uint96"},{"internalType":"uint96","name":"ethTotalBalanced","type":"uint96"},{"internalType":"uint96","name":"frxEthFree","type":"uint96"},{"internalType":"uint96","name":"frxEthInLpBalanced","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"depositEther","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"depositToAmoAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"frxETH","outputs":[{"internalType":"contract ERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_forceLive","type":"bool"},{"internalType":"bool","name":"_updateCache","type":"bool"}],"name":"getConsolidatedEthFrxEthBalance","outputs":[{"components":[{"internalType":"bool","name":"isStale","type":"bool"},{"internalType":"address","name":"amoAddress","type":"address"},{"internalType":"uint96","name":"ethFree","type":"uint96"},{"internalType":"uint96","name":"ethInLpBalanced","type":"uint96"},{"internalType":"uint96","name":"ethTotalBalanced","type":"uint96"},{"internalType":"uint96","name":"frxEthFree","type":"uint96"},{"internalType":"uint96","name":"frxEthInLpBalanced","type":"uint96"}],"internalType":"struct EtherRouter.CachedConsEFxBalances","name":"_rtnBalances","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_forceLive","type":"bool"}],"name":"getConsolidatedEthFrxEthBalanceView","outputs":[{"components":[{"internalType":"bool","name":"isStale","type":"bool"},{"internalType":"address","name":"amoAddress","type":"address"},{"internalType":"uint96","name":"ethFree","type":"uint96"},{"internalType":"uint96","name":"ethInLpBalanced","type":"uint96"},{"internalType":"uint96","name":"ethTotalBalanced","type":"uint96"},{"internalType":"uint96","name":"frxEthFree","type":"uint96"},{"internalType":"uint96","name":"frxEthInLpBalanced","type":"uint96"}],"internalType":"struct EtherRouter.CachedConsEFxBalances","name":"_rtnBalances","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lendingPool","outputs":[{"internalType":"contract LendingPool","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"operatorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingTimelockAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethRequested","type":"uint256"}],"name":"previewRequestEther","outputs":[{"internalType":"uint256","name":"_currEthInRouter","type":"uint256"},{"internalType":"uint256","name":"_rqShortage","type":"uint256"},{"internalType":"uint256","name":"_pullFromAmosAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"primaryWithdrawFromAmoAddr","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_tokenAmount","type":"uint256"}],"name":"recoverErc20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverEther","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"redemptionQueue","outputs":[{"internalType":"contract FraxEtherRedemptionQueueV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_amoAddress","type":"address"}],"name":"removeAmo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_ethRequested","type":"uint256"},{"internalType":"bool","name":"_bypassFullRqShortage","type":"bool"}],"name":"requestEther","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAddress","type":"address"}],"name":"setLendingPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOperatorAddress","type":"address"}],"name":"setOperatorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_depositToAddress","type":"address"},{"internalType":"address","name":"_withdrawFromAddress","type":"address"}],"name":"setPreferredDepositAndWithdrawalAMOs","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newAddress","type":"address"}],"name":"setRedemptionQueue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_depositAndVault","type":"bool"}],"name":"sweepEther","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"timelockAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newTimelock","type":"address"}],"name":"transferTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a060405234801561000f575f5ffd5b5060405161288b38038061288b83398101604081905261002e91610096565b5f80546001600160a01b031990811690915560018054821681556002805483166001600160a01b0395861617905560048054909216948416949094179055600592909255166080526100d6565b80516001600160a01b0381168114610091575f5ffd5b919050565b5f5f5f606084860312156100a8575f5ffd5b6100b18461007b565b92506100bf6020850161007b565b91506100cd6040850161007b565b90509250925092565b60805161279d6100ee5f395f610337015261279d5ff3fe6080604052600436106101b2575f3560e01c80638179cf66116100e7578063bda767ab11610087578063d27dadfc11610062578063d27dadfc14610599578063d6e51306146105b8578063f5da169c146105d7578063f6ccaad4146105f6575f5ffd5b8063bda767ab1461051d578063c3d314871461055b578063caa2db531461057a575f5ffd5b806398ea5fca116100c257806398ea5fca14610237578063a59a9973146104c1578063a780eb14146104df578063afb9a2cd146104fe575f5ffd5b80638179cf66146103b25780638ee5556e1461047657806397ec19be146104a2575f5ffd5b8063473b91d3116101525780634f8b4ae71161012d5780634f8b4ae714610312578063565d3e6e14610326578063738a8ce6146103595780637ae9fba914610378575f5ffd5b8063473b91d3146102b55780634bc66f32146102d45780634f128fcc146102f3575f5ffd5b8063127effb21161018d578063127effb2146102395780632f1d5a60146102585780633b4c46d0146102775780634501409514610296575f5ffd5b80630255d779146101bd578063090f3f50146101f9578063113aa8b114610218575f5ffd5b366101b957005b5f5ffd5b3480156101c8575f5ffd5b506101dc6101d736600461225d565b61060a565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610204575f5ffd5b506003546101dc906001600160a01b031681565b348015610223575f5ffd5b50610237610232366004612288565b610632565b005b348015610244575f5ffd5b506002546101dc906001600160a01b031681565b348015610263575f5ffd5b50610237610272366004612288565b610646565b348015610282575f5ffd5b50610237610291366004612288565b610657565b3480156102a1575f5ffd5b506102376102b0366004612288565b610692565b3480156102c0575f5ffd5b506102376102cf366004612288565b6106a3565b3480156102df575f5ffd5b506004546101dc906001600160a01b031681565b3480156102fe575f5ffd5b5061023761030d3660046122a3565b6107f7565b34801561031d575f5ffd5b506102376108cb565b348015610331575f5ffd5b506101dc7f000000000000000000000000000000000000000000000000000000000000000081565b348015610364575f5ffd5b506102376103733660046122da565b6108ef565b348015610383575f5ffd5b5061039761039236600461225d565b610950565b604080519384526020840192909252908201526060016101f0565b3480156103bd575f5ffd5b506104276103cc366004612288565b60086020525f9081526040902080546001820154600283015460039093015460ff8316936101009093046001600160a01b0316926001600160601b0380841693600160601b9081900482169382841693919091048216911687565b6040805197151588526001600160a01b0390961660208801526001600160601b0394851695870195909552918316606086015282166080850152811660a08401521660c082015260e0016101f0565b348015610481575f5ffd5b50610495610490366004612311565b6109f2565b6040516101f0919061232c565b3480156104ad575f5ffd5b506001546101dc906001600160a01b031681565b3480156104cc575f5ffd5b505f546101dc906001600160a01b031681565b3480156104ea575f5ffd5b506104956104f93660046123ba565b610a3b565b348015610509575f5ffd5b506102376105183660046123e6565b610c24565b348015610528575f5ffd5b5061054b610537366004612288565b60076020525f908152604090205460ff1681565b60405190151581526020016101f0565b348015610566575f5ffd5b5061023761057536600461225d565b61106a565b348015610585575f5ffd5b50610237610594366004612409565b611113565b3480156105a4575f5ffd5b506102376105b3366004612288565b611689565b3480156105c3575f5ffd5b50600a546101dc906001600160a01b031681565b3480156105e2575f5ffd5b506009546101dc906001600160a01b031681565b348015610601575f5ffd5b50610237611856565b60068181548110610619575f80fd5b5f918252602090912001546001600160a01b0316905081565b61063a611866565b6106438161186f565b50565b61064e611866565b610643816118c8565b61065f611866565b6001546001600160a01b03161561068957604051634d8ee7eb60e01b815260040160405180910390fd5b61064381611923565b61069a611866565b6106438161197e565b6106ab611866565b6001600160a01b0381166106d25760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081526007602052604090205460ff1661070a576040516315c58f1b60e01b815260040160405180910390fd5b6001600160a01b0381165f908152600760205260408120805460ff191690555b6006548110156107b657816001600160a01b03166006828154811061075157610751612448565b5f918252602090912001546001600160a01b0316036107ae575f6006828154811061077e5761077e612448565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055506107b6565b60010161072a565b506040516001600160a01b03821681527f3fafffa29b035aac1c23ef5c6f965b7f1dcf70b1b14493f1bb2ba21aa5501fda906020015b60405180910390a150565b6107ff6119cf565b6001600160a01b0382165f9081526007602052604090205460ff16158061083e57506001600160a01b0381165f9081526007602052604090205460ff16155b1561085c576040516302b7bc5d60e01b815260040160405180910390fd5b600980546001600160a01b038481166001600160a01b03199283168117909355600a80549185169190921681179091556040805192835260208301919091527f35c12b45467d21293c6eb8dd374511d02b408e98c158636be7082212f6da464991015b60405180910390a15050565b6108d3611866565b6108db611a0f565b6108e45f61197e565b6108ed5f611a18565b565b6108f7611866565b600454610911906001600160a01b03848116911683611a73565b604080516001600160a01b0384168152602081018390527fa75ce34872c395a628c61aff7fee33ef71bed972e3a28c7f63fff0022f629aa991016108bf565b600154604080516309c79ceb60e41b8152815147935f9384936001600160a01b0390921692639c79ceb09260048082019392918290030181865afa15801561099a573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109be919061245c565b92508390506109cd8386612492565b11156109eb57826109de8386612492565b6109e891906124a5565b90505b9193909250565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810191909152610a34825f611ada565b5092915050565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915260608215610c1157610a86846001611ada565b90925090505f5b8151811015610c0b575f828281518110610aa957610aa9612448565b60200260200101516020015190505f6001600160a01b0316816001600160a01b031614610c0257828281518110610ae257610ae2612448565b6020908102919091018101516001600160a01b038381165f9081526008845260409081902083518154958501517fffffffffffffffffffffff0000000000000000000000000000000000000000009096169015157fffffffffffffffffffffff0000000000000000000000000000000000000000ff1617610100959093169490940291909117835581015160018301805460608401516001600160601b039384167fffffffffffffffff00000000000000000000000000000000000000000000000092831617600160601b918516820217909255608084015160028601805460a087015192861693169290921790841690920291909117905560c090910151600390920180546bffffffffffffffffffffffff1916929091169190911790555b50600101610a8d565b50610a34565b610c1b845f611ada565b50949350505050565b610c2c6119cf565b5f8054604051631c6c959760e01b815260048101929092526001600160a01b031690631c6c959790602401610100604051808303815f875af1158015610c74573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c98919061254c565b5050505050815f03610ca8574791505b600154604080516309c79ceb60e41b815281515f936001600160a01b031692639c79ceb092600480820193918290030181865afa158015610ceb573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d0f919061245c565b915050808311610ddd576001546040515f916001600160a01b03169085908381818185875af1925050503d805f8114610d63576040519150601f19603f3d011682016040523d82523d5f602084013e610d68565b606091505b5050905080610d91576040516316a71b2160e01b81525f60048201526024015b60405180910390fd5b600154604080516001600160a01b039092168252602082018690527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a150611010565b8015610e9f576001546040515f916001600160a01b03169083908381818185875af1925050503d805f8114610e2d576040519150601f19603f3d011682016040523d82523d5f602084013e610e32565b606091505b5050905080610e57576040516316a71b2160e01b815260016004820152602401610d88565b600154604080516001600160a01b039092168252602082018490527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a1505b5f610eaa82856124a5565b6009549091506001600160a01b031615610fc8578215610f2d5760095f9054906101000a90046001600160a01b03166001600160a01b03166398ea5fca826040518263ffffffff1660e01b81526004015f604051808303818588803b158015610f11575f5ffd5b505af1158015610f23573d5f5f3e3d5ffd5b5050505050610fa3565b6009546040515f916001600160a01b03169083908381818185875af1925050503d805f8114610f77576040519150601f19603f3d011682016040523d82523d5f602084013e610f7c565b606091505b5050905080610fa1576040516316a71b2160e01b815260026004820152602401610d88565b505b6009546001600160a01b03165f908152600860205260409020805460ff191660011790555b600954604080516001600160a01b039092168252602082018390527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a1505b5f80546040805163552a7ac760e01b815290516001600160a01b039092169263552a7ac79260048084019382900301818387803b15801561104f575f5ffd5b505af1158015611061573d5f5f3e3d5ffd5b50505050505050565b611072611866565b6004546040515f916001600160a01b03169083908381818185875af1925050503d805f81146110bc576040519150601f19603f3d011682016040523d82523d5f602084013e6110c1565b606091505b50509050806110e35760405163c9571e5160e01b815260040160405180910390fd5b6040518281527fded9e5219cc8afc37a129b46dc375a8d1bfc8571d6ba63b3366509b687c23419906020016108bf565b61111b611f89565b611123611fe2565b5f8054604051636dc77d5160e11b815260048101929092526001600160a01b03169063db8efaa290602401610100604051808303815f875af115801561116b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061118f919061254c565b50505050505f5f5f6111a085610950565b919450925090508084156111c9578386116111bc57505f6111c9565b6111c684876124a5565b90505b80156114b8576006545f9067ffffffffffffffff8111156111ec576111ec6124b8565b604051908082528060200260200182016040528015611215578160200160208202803683370190505b50600a549091506001600160a01b03161561132057600a5481516001600160a01b039091169082905f9061124b5761124b612448565b6001600160a01b039092166020928302919091019091015260015f5b60065481101561131957600a54600680546001600160a01b03909216918390811061129457611294612448565b5f918252602090912001546001600160a01b03161461131157600681815481106112c0576112c0612448565b905f5260205f20015f9054906101000a90046001600160a01b03168383815181106112ed576112ed612448565b6001600160a01b039092166020928302919091019091015261130e826125f8565b91505b600101611267565b505061137c565b600680548060200260200160405190810160405280929190818152602001828054801561137457602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311611356575b505050505090505b5f5b81518110156114b5575f6001600160a01b03168282815181106113a3576113a3612448565b60200260200101516001600160a01b0316146114b0575f8282815181106113cc576113cc612448565b60200260200101516001600160a01b031663cb97bfe4856040518263ffffffff1660e01b815260040161140191815260200190565b60408051808303815f875af115801561141c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611440919061245c565b50905061144d81856124a5565b9350801561149e57600160085f85858151811061146c5761146c612448565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff19169115159190911790555b835f036114ab57506114b5565b506001015b61137e565b50505b80156114da57604051633c59e0e160e21b815260048101829052602401610d88565b841580156114e757505f83115b15611562576001546040515f916001600160a01b03169085908381818185875af1925050503d805f8114611536576040519150601f19603f3d011682016040523d82523d5f602084013e61153b565b606091505b5050905080611560576040516316a71b2160e01b815260026004820152602401610d88565b505b5f876001600160a01b0316876040515f6040518083038185875af1925050503d805f81146115ab576040519150601f19603f3d011682016040523d82523d5f602084013e6115b0565b606091505b50509050806115d5576040516316a71b2160e01b815260036004820152602401610d88565b5f80546040805163552a7ac760e01b815290516001600160a01b039092169263552a7ac79260048084019382900301818387803b158015611614575f5ffd5b505af1158015611626573d5f5f3e3d5ffd5b5050604080516001600160a01b038c168152602081018b90529081018790527fd6bd91b23a2c9da85df3ae37aaf53d44be8b085e0117cae30eccf2ef3a4b9c2c9250606001905060405180910390a150505050506116846001600555565b505050565b611691611866565b6001600160a01b0381166116b85760405163d92e233d60e01b815260040160405180910390fd5b806001600160a01b031663855d0b706040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116f4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117189190612610565b60405163338d394f60e11b81526001600160a01b038381166004830152919091169063671a729e9060240160a060405180830381865afa15801561175e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611782919061262b565b505050506001600160a01b0382165f9081526007602052604090205460ff161590506117c15760405163de44fc9d60e01b815260040160405180910390fd5b6001600160a01b0381165f818152600760209081526040808320805460ff191660019081179091556006805491820181559093527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f90920180546001600160a01b0319168417905590519182527fd40d55f1a2677846d4b42559084a050c7cc0fb727533c3f7ac46b4c6817c1a8991016107ec565b61185e611a0f565b6108ed612021565b6108ed3361203a565b5f80546040516001600160a01b03808516939216917fb9f6be0808205b56830ade3fd9ac1dbae6026b9e22578da1b18a4392434b0ed091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b6002546040516001600160a01b038084169216907f74da04524d50c64947f5dd5381ef1a4dca5cba8ed1d816243f9e48aa0b5617ed905f90a3600280546001600160a01b0319166001600160a01b0392909216919091179055565b6001546040516001600160a01b038084169216907f424a9ddccdce2977b7b36137445246a4423569d7b793bf5d483c21278fa77dd1905f90a3600180546001600160a01b0319166001600160a01b0392909216919091179055565b600380546001600160a01b0319166001600160a01b03838116918217909255600454604051919216907f162998b90abc2507f3953aa797827b03a14c42dbd9a35f09feaf02e0d592773a905f90a350565b6004546001600160a01b03163314806119f257506002546001600160a01b031633145b6108ed57604051631566fd5360e21b815260040160405180910390fd5b6108ed33612081565b6004546040516001600160a01b038084169216907f31b6c5a04b069b6ec1b3cef44c4e7c1eadd721349cda9823d0b1877b3551cdc6905f90a3600480546001600160a01b0319166001600160a01b0392909216919091179055565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1663a9059cbb60e01b1790526116849084906120c6565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091526006546060905f9067ffffffffffffffff811115611b3257611b326124b8565b604051908082528060200260200182016040528015611b9757816020015b6040805160e0810182525f8082526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282525f19909201910181611b505790505b5090504783604001818151611bac9190612667565b6001600160601b0316905250608083018051479190611bcc908390612667565b6001600160601b03169052505f5b600654811015611f7d575f60068281548110611bf857611bf8612448565b5f918252602090912001546001600160a01b031690508015611f74576001600160a01b038082165f90815260086020908152604091829020825160e081018452815460ff811615158083526101009091049095169281019290925260018101546001600160601b0380821694840194909452600160601b90819004841660608401526002820154808516608085015204831660a08301526003015490911660c08201529080611ca45750875b15611ec7575f826001600160a01b031663855d0b706040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ce6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d0a9190612610565b6040516357ef3c2d60e11b81526001600160a01b038581166004830152919091169063afde785a9060240160a060405180830381865afa158015611d50573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d74919061269c565b9050805f015187604001818151611d8b9190612667565b6001600160601b03169052506020810151606088018051611dad908390612667565b6001600160601b03169052506040810151608088018051611dcf908390612667565b6001600160601b0316905250606081015160a088018051611df1908390612667565b6001600160601b0316905250608081015160c088018051611e13908390612667565b6001600160601b03169052508715611ec1576040518060e001604052805f15158152602001846001600160a01b03168152602001825f01516001600160601b0316815260200182602001516001600160601b0316815260200182604001516001600160601b0316815260200182606001516001600160601b0316815260200182608001516001600160601b0316815250858581518110611eb557611eb5612448565b60200260200101819052505b50611f72565b806040015186604001818151611edd9190612667565b6001600160601b03169052506060808201519087018051611eff908390612667565b6001600160601b03169052506080808201519087018051611f21908390612667565b6001600160601b031690525060a0808201519087018051611f43908390612667565b6001600160601b031690525060c0808201519087018051611f65908390612667565b6001600160601b03169052505b505b50600101611bda565b50809150509250929050565b600260055403611fdb5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610d88565b6002600555565b5f546001600160a01b031633148061200457506001546001600160a01b031633145b6108ed576040516302c89dbf60e61b815260040160405180910390fd5b600380546001600160a01b03191690556108ed33611a18565b6004546001600160a01b03828116911614610643576004805460405163110f70ad60e21b81526001600160a01b039182169281019290925282166024820152604401610d88565b6003546001600160a01b038281169116146106435760035460405163be5a953760e01b81526001600160a01b0391821660048201529082166024820152604401610d88565b5f6120da6001600160a01b03841683612127565b905080515f141580156120fe5750808060200190518101906120fc9190612736565b155b1561168457604051635274afe760e01b81526001600160a01b0384166004820152602401610d88565b606061213483835f61213d565b90505b92915050565b6060814710156121625760405163cd78605960e01b8152306004820152602401610d88565b5f5f856001600160a01b0316848660405161217d9190612751565b5f6040518083038185875af1925050503d805f81146121b7576040519150601f19603f3d011682016040523d82523d5f602084013e6121bc565b606091505b50915091506121cc8683836121d8565b925050505b9392505050565b6060826121ed576121e882612234565b6121d1565b815115801561220457506001600160a01b0384163b155b1561222d57604051639996b31560e01b81526001600160a01b0385166004820152602401610d88565b50806121d1565b8051156122445780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f6020828403121561226d575f5ffd5b5035919050565b6001600160a01b0381168114610643575f5ffd5b5f60208284031215612298575f5ffd5b81356121d181612274565b5f5f604083850312156122b4575f5ffd5b82356122bf81612274565b915060208301356122cf81612274565b809150509250929050565b5f5f604083850312156122eb575f5ffd5b82356122f681612274565b946020939093013593505050565b8015158114610643575f5ffd5b5f60208284031215612321575f5ffd5b81356121d181612304565b5f60e0820190508251151582526001600160a01b0360208401511660208301526001600160601b0360408401511660408301526001600160601b0360608401511660608301526001600160601b03608084015116608083015260a083015161239f60a08401826001600160601b03169052565b5060c0830151610a3460c08401826001600160601b03169052565b5f5f604083850312156123cb575f5ffd5b82356123d681612304565b915060208301356122cf81612304565b5f5f604083850312156123f7575f5ffd5b8235915060208301356122cf81612304565b5f5f5f6060848603121561241b575f5ffd5b833561242681612274565b925060208401359150604084013561243d81612304565b809150509250925092565b634e487b7160e01b5f52603260045260245ffd5b5f5f6040838503121561246d575f5ffd5b505080516020909101519092909150565b634e487b7160e01b5f52601160045260245ffd5b808201808211156121375761213761247e565b818103818111156121375761213761247e565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156124fb57634e487b7160e01b5f52604160045260245ffd5b60405290565b6040805190810167ffffffffffffffff811182821017156124fb57634e487b7160e01b5f52604160045260245ffd5b805167ffffffffffffffff81168114612547575f5ffd5b919050565b5f5f5f5f5f858703610100811215612562575f5ffd5b865160208801516040890151919750955093506060605f1982011215612586575f5ffd5b61258e6124cc565b61259a60608901612530565b81526125a860808901612530565b60208201526125b960a08901612530565b604082015280935050604060bf19820112156125d3575f5ffd5b506125dc612501565b60c0870151815260e09096015160208701525092959194509290565b5f600182016126095761260961247e565b5060010190565b5f60208284031215612620575f5ffd5b81516121d181612274565b5f5f5f5f5f60a0868803121561263f575f5ffd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b6001600160601b0381811683821601908111156121375761213761247e565b80516001600160601b0381168114612547575f5ffd5b5f60a08284031280156126ad575f5ffd5b5060405160a0810167ffffffffffffffff811182821017156126dd57634e487b7160e01b5f52604160045260245ffd5b6040526126e983612686565b81526126f760208401612686565b602082015261270860408401612686565b604082015261271960608401612686565b606082015261272a60808401612686565b60808201529392505050565b5f60208284031215612746575f5ffd5b81516121d181612304565b5f82518060208501845e5f92019182525091905056fea2646970667358221220a5301dd01aff0ef7cf272b97345073225480482f43c43d6200cf0469e9d7e6b164736f6c634300081c00330000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c0000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c0000000000000000000000005e8422345238f34275888049021821e8e08caa1f

Deployed Bytecode

0x6080604052600436106101b2575f3560e01c80638179cf66116100e7578063bda767ab11610087578063d27dadfc11610062578063d27dadfc14610599578063d6e51306146105b8578063f5da169c146105d7578063f6ccaad4146105f6575f5ffd5b8063bda767ab1461051d578063c3d314871461055b578063caa2db531461057a575f5ffd5b806398ea5fca116100c257806398ea5fca14610237578063a59a9973146104c1578063a780eb14146104df578063afb9a2cd146104fe575f5ffd5b80638179cf66146103b25780638ee5556e1461047657806397ec19be146104a2575f5ffd5b8063473b91d3116101525780634f8b4ae71161012d5780634f8b4ae714610312578063565d3e6e14610326578063738a8ce6146103595780637ae9fba914610378575f5ffd5b8063473b91d3146102b55780634bc66f32146102d45780634f128fcc146102f3575f5ffd5b8063127effb21161018d578063127effb2146102395780632f1d5a60146102585780633b4c46d0146102775780634501409514610296575f5ffd5b80630255d779146101bd578063090f3f50146101f9578063113aa8b114610218575f5ffd5b366101b957005b5f5ffd5b3480156101c8575f5ffd5b506101dc6101d736600461225d565b61060a565b6040516001600160a01b0390911681526020015b60405180910390f35b348015610204575f5ffd5b506003546101dc906001600160a01b031681565b348015610223575f5ffd5b50610237610232366004612288565b610632565b005b348015610244575f5ffd5b506002546101dc906001600160a01b031681565b348015610263575f5ffd5b50610237610272366004612288565b610646565b348015610282575f5ffd5b50610237610291366004612288565b610657565b3480156102a1575f5ffd5b506102376102b0366004612288565b610692565b3480156102c0575f5ffd5b506102376102cf366004612288565b6106a3565b3480156102df575f5ffd5b506004546101dc906001600160a01b031681565b3480156102fe575f5ffd5b5061023761030d3660046122a3565b6107f7565b34801561031d575f5ffd5b506102376108cb565b348015610331575f5ffd5b506101dc7f0000000000000000000000005e8422345238f34275888049021821e8e08caa1f81565b348015610364575f5ffd5b506102376103733660046122da565b6108ef565b348015610383575f5ffd5b5061039761039236600461225d565b610950565b604080519384526020840192909252908201526060016101f0565b3480156103bd575f5ffd5b506104276103cc366004612288565b60086020525f9081526040902080546001820154600283015460039093015460ff8316936101009093046001600160a01b0316926001600160601b0380841693600160601b9081900482169382841693919091048216911687565b6040805197151588526001600160a01b0390961660208801526001600160601b0394851695870195909552918316606086015282166080850152811660a08401521660c082015260e0016101f0565b348015610481575f5ffd5b50610495610490366004612311565b6109f2565b6040516101f0919061232c565b3480156104ad575f5ffd5b506001546101dc906001600160a01b031681565b3480156104cc575f5ffd5b505f546101dc906001600160a01b031681565b3480156104ea575f5ffd5b506104956104f93660046123ba565b610a3b565b348015610509575f5ffd5b506102376105183660046123e6565b610c24565b348015610528575f5ffd5b5061054b610537366004612288565b60076020525f908152604090205460ff1681565b60405190151581526020016101f0565b348015610566575f5ffd5b5061023761057536600461225d565b61106a565b348015610585575f5ffd5b50610237610594366004612409565b611113565b3480156105a4575f5ffd5b506102376105b3366004612288565b611689565b3480156105c3575f5ffd5b50600a546101dc906001600160a01b031681565b3480156105e2575f5ffd5b506009546101dc906001600160a01b031681565b348015610601575f5ffd5b50610237611856565b60068181548110610619575f80fd5b5f918252602090912001546001600160a01b0316905081565b61063a611866565b6106438161186f565b50565b61064e611866565b610643816118c8565b61065f611866565b6001546001600160a01b03161561068957604051634d8ee7eb60e01b815260040160405180910390fd5b61064381611923565b61069a611866565b6106438161197e565b6106ab611866565b6001600160a01b0381166106d25760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081526007602052604090205460ff1661070a576040516315c58f1b60e01b815260040160405180910390fd5b6001600160a01b0381165f908152600760205260408120805460ff191690555b6006548110156107b657816001600160a01b03166006828154811061075157610751612448565b5f918252602090912001546001600160a01b0316036107ae575f6006828154811061077e5761077e612448565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055506107b6565b60010161072a565b506040516001600160a01b03821681527f3fafffa29b035aac1c23ef5c6f965b7f1dcf70b1b14493f1bb2ba21aa5501fda906020015b60405180910390a150565b6107ff6119cf565b6001600160a01b0382165f9081526007602052604090205460ff16158061083e57506001600160a01b0381165f9081526007602052604090205460ff16155b1561085c576040516302b7bc5d60e01b815260040160405180910390fd5b600980546001600160a01b038481166001600160a01b03199283168117909355600a80549185169190921681179091556040805192835260208301919091527f35c12b45467d21293c6eb8dd374511d02b408e98c158636be7082212f6da464991015b60405180910390a15050565b6108d3611866565b6108db611a0f565b6108e45f61197e565b6108ed5f611a18565b565b6108f7611866565b600454610911906001600160a01b03848116911683611a73565b604080516001600160a01b0384168152602081018390527fa75ce34872c395a628c61aff7fee33ef71bed972e3a28c7f63fff0022f629aa991016108bf565b600154604080516309c79ceb60e41b8152815147935f9384936001600160a01b0390921692639c79ceb09260048082019392918290030181865afa15801561099a573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906109be919061245c565b92508390506109cd8386612492565b11156109eb57826109de8386612492565b6109e891906124a5565b90505b9193909250565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810191909152610a34825f611ada565b5092915050565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c081019190915260608215610c1157610a86846001611ada565b90925090505f5b8151811015610c0b575f828281518110610aa957610aa9612448565b60200260200101516020015190505f6001600160a01b0316816001600160a01b031614610c0257828281518110610ae257610ae2612448565b6020908102919091018101516001600160a01b038381165f9081526008845260409081902083518154958501517fffffffffffffffffffffff0000000000000000000000000000000000000000009096169015157fffffffffffffffffffffff0000000000000000000000000000000000000000ff1617610100959093169490940291909117835581015160018301805460608401516001600160601b039384167fffffffffffffffff00000000000000000000000000000000000000000000000092831617600160601b918516820217909255608084015160028601805460a087015192861693169290921790841690920291909117905560c090910151600390920180546bffffffffffffffffffffffff1916929091169190911790555b50600101610a8d565b50610a34565b610c1b845f611ada565b50949350505050565b610c2c6119cf565b5f8054604051631c6c959760e01b815260048101929092526001600160a01b031690631c6c959790602401610100604051808303815f875af1158015610c74573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c98919061254c565b5050505050815f03610ca8574791505b600154604080516309c79ceb60e41b815281515f936001600160a01b031692639c79ceb092600480820193918290030181865afa158015610ceb573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d0f919061245c565b915050808311610ddd576001546040515f916001600160a01b03169085908381818185875af1925050503d805f8114610d63576040519150601f19603f3d011682016040523d82523d5f602084013e610d68565b606091505b5050905080610d91576040516316a71b2160e01b81525f60048201526024015b60405180910390fd5b600154604080516001600160a01b039092168252602082018690527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a150611010565b8015610e9f576001546040515f916001600160a01b03169083908381818185875af1925050503d805f8114610e2d576040519150601f19603f3d011682016040523d82523d5f602084013e610e32565b606091505b5050905080610e57576040516316a71b2160e01b815260016004820152602401610d88565b600154604080516001600160a01b039092168252602082018490527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a1505b5f610eaa82856124a5565b6009549091506001600160a01b031615610fc8578215610f2d5760095f9054906101000a90046001600160a01b03166001600160a01b03166398ea5fca826040518263ffffffff1660e01b81526004015f604051808303818588803b158015610f11575f5ffd5b505af1158015610f23573d5f5f3e3d5ffd5b5050505050610fa3565b6009546040515f916001600160a01b03169083908381818185875af1925050503d805f8114610f77576040519150601f19603f3d011682016040523d82523d5f602084013e610f7c565b606091505b5050905080610fa1576040516316a71b2160e01b815260026004820152602401610d88565b505b6009546001600160a01b03165f908152600860205260409020805460ff191660011790555b600954604080516001600160a01b039092168252602082018390527f223b3b5b715255297a2e5df768eaaaf12cffe343adb6f8a0d096275c36e05f1c910160405180910390a1505b5f80546040805163552a7ac760e01b815290516001600160a01b039092169263552a7ac79260048084019382900301818387803b15801561104f575f5ffd5b505af1158015611061573d5f5f3e3d5ffd5b50505050505050565b611072611866565b6004546040515f916001600160a01b03169083908381818185875af1925050503d805f81146110bc576040519150601f19603f3d011682016040523d82523d5f602084013e6110c1565b606091505b50509050806110e35760405163c9571e5160e01b815260040160405180910390fd5b6040518281527fded9e5219cc8afc37a129b46dc375a8d1bfc8571d6ba63b3366509b687c23419906020016108bf565b61111b611f89565b611123611fe2565b5f8054604051636dc77d5160e11b815260048101929092526001600160a01b03169063db8efaa290602401610100604051808303815f875af115801561116b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061118f919061254c565b50505050505f5f5f6111a085610950565b919450925090508084156111c9578386116111bc57505f6111c9565b6111c684876124a5565b90505b80156114b8576006545f9067ffffffffffffffff8111156111ec576111ec6124b8565b604051908082528060200260200182016040528015611215578160200160208202803683370190505b50600a549091506001600160a01b03161561132057600a5481516001600160a01b039091169082905f9061124b5761124b612448565b6001600160a01b039092166020928302919091019091015260015f5b60065481101561131957600a54600680546001600160a01b03909216918390811061129457611294612448565b5f918252602090912001546001600160a01b03161461131157600681815481106112c0576112c0612448565b905f5260205f20015f9054906101000a90046001600160a01b03168383815181106112ed576112ed612448565b6001600160a01b039092166020928302919091019091015261130e826125f8565b91505b600101611267565b505061137c565b600680548060200260200160405190810160405280929190818152602001828054801561137457602002820191905f5260205f20905b81546001600160a01b03168152600190910190602001808311611356575b505050505090505b5f5b81518110156114b5575f6001600160a01b03168282815181106113a3576113a3612448565b60200260200101516001600160a01b0316146114b0575f8282815181106113cc576113cc612448565b60200260200101516001600160a01b031663cb97bfe4856040518263ffffffff1660e01b815260040161140191815260200190565b60408051808303815f875af115801561141c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611440919061245c565b50905061144d81856124a5565b9350801561149e57600160085f85858151811061146c5761146c612448565b6020908102919091018101516001600160a01b031682528101919091526040015f20805460ff19169115159190911790555b835f036114ab57506114b5565b506001015b61137e565b50505b80156114da57604051633c59e0e160e21b815260048101829052602401610d88565b841580156114e757505f83115b15611562576001546040515f916001600160a01b03169085908381818185875af1925050503d805f8114611536576040519150601f19603f3d011682016040523d82523d5f602084013e61153b565b606091505b5050905080611560576040516316a71b2160e01b815260026004820152602401610d88565b505b5f876001600160a01b0316876040515f6040518083038185875af1925050503d805f81146115ab576040519150601f19603f3d011682016040523d82523d5f602084013e6115b0565b606091505b50509050806115d5576040516316a71b2160e01b815260036004820152602401610d88565b5f80546040805163552a7ac760e01b815290516001600160a01b039092169263552a7ac79260048084019382900301818387803b158015611614575f5ffd5b505af1158015611626573d5f5f3e3d5ffd5b5050604080516001600160a01b038c168152602081018b90529081018790527fd6bd91b23a2c9da85df3ae37aaf53d44be8b085e0117cae30eccf2ef3a4b9c2c9250606001905060405180910390a150505050506116846001600555565b505050565b611691611866565b6001600160a01b0381166116b85760405163d92e233d60e01b815260040160405180910390fd5b806001600160a01b031663855d0b706040518163ffffffff1660e01b8152600401602060405180830381865afa1580156116f4573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117189190612610565b60405163338d394f60e11b81526001600160a01b038381166004830152919091169063671a729e9060240160a060405180830381865afa15801561175e573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611782919061262b565b505050506001600160a01b0382165f9081526007602052604090205460ff161590506117c15760405163de44fc9d60e01b815260040160405180910390fd5b6001600160a01b0381165f818152600760209081526040808320805460ff191660019081179091556006805491820181559093527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f90920180546001600160a01b0319168417905590519182527fd40d55f1a2677846d4b42559084a050c7cc0fb727533c3f7ac46b4c6817c1a8991016107ec565b61185e611a0f565b6108ed612021565b6108ed3361203a565b5f80546040516001600160a01b03808516939216917fb9f6be0808205b56830ade3fd9ac1dbae6026b9e22578da1b18a4392434b0ed091a35f80546001600160a01b0319166001600160a01b0392909216919091179055565b6002546040516001600160a01b038084169216907f74da04524d50c64947f5dd5381ef1a4dca5cba8ed1d816243f9e48aa0b5617ed905f90a3600280546001600160a01b0319166001600160a01b0392909216919091179055565b6001546040516001600160a01b038084169216907f424a9ddccdce2977b7b36137445246a4423569d7b793bf5d483c21278fa77dd1905f90a3600180546001600160a01b0319166001600160a01b0392909216919091179055565b600380546001600160a01b0319166001600160a01b03838116918217909255600454604051919216907f162998b90abc2507f3953aa797827b03a14c42dbd9a35f09feaf02e0d592773a905f90a350565b6004546001600160a01b03163314806119f257506002546001600160a01b031633145b6108ed57604051631566fd5360e21b815260040160405180910390fd5b6108ed33612081565b6004546040516001600160a01b038084169216907f31b6c5a04b069b6ec1b3cef44c4e7c1eadd721349cda9823d0b1877b3551cdc6905f90a3600480546001600160a01b0319166001600160a01b0392909216919091179055565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1663a9059cbb60e01b1790526116849084906120c6565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091526006546060905f9067ffffffffffffffff811115611b3257611b326124b8565b604051908082528060200260200182016040528015611b9757816020015b6040805160e0810182525f8082526020808301829052928201819052606082018190526080820181905260a0820181905260c082015282525f19909201910181611b505790505b5090504783604001818151611bac9190612667565b6001600160601b0316905250608083018051479190611bcc908390612667565b6001600160601b03169052505f5b600654811015611f7d575f60068281548110611bf857611bf8612448565b5f918252602090912001546001600160a01b031690508015611f74576001600160a01b038082165f90815260086020908152604091829020825160e081018452815460ff811615158083526101009091049095169281019290925260018101546001600160601b0380821694840194909452600160601b90819004841660608401526002820154808516608085015204831660a08301526003015490911660c08201529080611ca45750875b15611ec7575f826001600160a01b031663855d0b706040518163ffffffff1660e01b8152600401602060405180830381865afa158015611ce6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d0a9190612610565b6040516357ef3c2d60e11b81526001600160a01b038581166004830152919091169063afde785a9060240160a060405180830381865afa158015611d50573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611d74919061269c565b9050805f015187604001818151611d8b9190612667565b6001600160601b03169052506020810151606088018051611dad908390612667565b6001600160601b03169052506040810151608088018051611dcf908390612667565b6001600160601b0316905250606081015160a088018051611df1908390612667565b6001600160601b0316905250608081015160c088018051611e13908390612667565b6001600160601b03169052508715611ec1576040518060e001604052805f15158152602001846001600160a01b03168152602001825f01516001600160601b0316815260200182602001516001600160601b0316815260200182604001516001600160601b0316815260200182606001516001600160601b0316815260200182608001516001600160601b0316815250858581518110611eb557611eb5612448565b60200260200101819052505b50611f72565b806040015186604001818151611edd9190612667565b6001600160601b03169052506060808201519087018051611eff908390612667565b6001600160601b03169052506080808201519087018051611f21908390612667565b6001600160601b031690525060a0808201519087018051611f43908390612667565b6001600160601b031690525060c0808201519087018051611f65908390612667565b6001600160601b03169052505b505b50600101611bda565b50809150509250929050565b600260055403611fdb5760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610d88565b6002600555565b5f546001600160a01b031633148061200457506001546001600160a01b031633145b6108ed576040516302c89dbf60e61b815260040160405180910390fd5b600380546001600160a01b03191690556108ed33611a18565b6004546001600160a01b03828116911614610643576004805460405163110f70ad60e21b81526001600160a01b039182169281019290925282166024820152604401610d88565b6003546001600160a01b038281169116146106435760035460405163be5a953760e01b81526001600160a01b0391821660048201529082166024820152604401610d88565b5f6120da6001600160a01b03841683612127565b905080515f141580156120fe5750808060200190518101906120fc9190612736565b155b1561168457604051635274afe760e01b81526001600160a01b0384166004820152602401610d88565b606061213483835f61213d565b90505b92915050565b6060814710156121625760405163cd78605960e01b8152306004820152602401610d88565b5f5f856001600160a01b0316848660405161217d9190612751565b5f6040518083038185875af1925050503d805f81146121b7576040519150601f19603f3d011682016040523d82523d5f602084013e6121bc565b606091505b50915091506121cc8683836121d8565b925050505b9392505050565b6060826121ed576121e882612234565b6121d1565b815115801561220457506001600160a01b0384163b155b1561222d57604051639996b31560e01b81526001600160a01b0385166004820152602401610d88565b50806121d1565b8051156122445780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b5f6020828403121561226d575f5ffd5b5035919050565b6001600160a01b0381168114610643575f5ffd5b5f60208284031215612298575f5ffd5b81356121d181612274565b5f5f604083850312156122b4575f5ffd5b82356122bf81612274565b915060208301356122cf81612274565b809150509250929050565b5f5f604083850312156122eb575f5ffd5b82356122f681612274565b946020939093013593505050565b8015158114610643575f5ffd5b5f60208284031215612321575f5ffd5b81356121d181612304565b5f60e0820190508251151582526001600160a01b0360208401511660208301526001600160601b0360408401511660408301526001600160601b0360608401511660608301526001600160601b03608084015116608083015260a083015161239f60a08401826001600160601b03169052565b5060c0830151610a3460c08401826001600160601b03169052565b5f5f604083850312156123cb575f5ffd5b82356123d681612304565b915060208301356122cf81612304565b5f5f604083850312156123f7575f5ffd5b8235915060208301356122cf81612304565b5f5f5f6060848603121561241b575f5ffd5b833561242681612274565b925060208401359150604084013561243d81612304565b809150509250925092565b634e487b7160e01b5f52603260045260245ffd5b5f5f6040838503121561246d575f5ffd5b505080516020909101519092909150565b634e487b7160e01b5f52601160045260245ffd5b808201808211156121375761213761247e565b818103818111156121375761213761247e565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156124fb57634e487b7160e01b5f52604160045260245ffd5b60405290565b6040805190810167ffffffffffffffff811182821017156124fb57634e487b7160e01b5f52604160045260245ffd5b805167ffffffffffffffff81168114612547575f5ffd5b919050565b5f5f5f5f5f858703610100811215612562575f5ffd5b865160208801516040890151919750955093506060605f1982011215612586575f5ffd5b61258e6124cc565b61259a60608901612530565b81526125a860808901612530565b60208201526125b960a08901612530565b604082015280935050604060bf19820112156125d3575f5ffd5b506125dc612501565b60c0870151815260e09096015160208701525092959194509290565b5f600182016126095761260961247e565b5060010190565b5f60208284031215612620575f5ffd5b81516121d181612274565b5f5f5f5f5f60a0868803121561263f575f5ffd5b5050835160208501516040860151606087015160809097015192989197509594509092509050565b6001600160601b0381811683821601908111156121375761213761247e565b80516001600160601b0381168114612547575f5ffd5b5f60a08284031280156126ad575f5ffd5b5060405160a0810167ffffffffffffffff811182821017156126dd57634e487b7160e01b5f52604160045260245ffd5b6040526126e983612686565b81526126f760208401612686565b602082015261270860408401612686565b604082015261271960608401612686565b606082015261272a60808401612686565b60808201529392505050565b5f60208284031215612746575f5ffd5b81516121d181612304565b5f82518060208501845e5f92019182525091905056fea2646970667358221220a5301dd01aff0ef7cf272b97345073225480482f43c43d6200cf0469e9d7e6b164736f6c634300081c0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c0000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c0000000000000000000000005e8422345238f34275888049021821e8e08caa1f

-----Decoded View---------------
Arg [0] : _timelockAddress (address): 0x8306300ffd616049FD7e4b0354a64Da835c1A81C
Arg [1] : _operatorAddress (address): 0x8306300ffd616049FD7e4b0354a64Da835c1A81C
Arg [2] : _frxEthAddress (address): 0x5E8422345238F34275888049021821E8E08CAa1f

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c
Arg [1] : 0000000000000000000000008306300ffd616049fd7e4b0354a64da835c1a81c
Arg [2] : 0000000000000000000000005e8422345238f34275888049021821e8e08caa1f


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.