// File: src/contracts/SlippageAuction.sol



// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================= SlippageAuction ==========================
// ====================================================================
// Dutch-style Auction. Starts at a high price and gradually decreases until the entire lot
// of tokens is sold, or the time expires.
// Frax Finance: https://github.com/FraxFinance

import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { IUniswapV2Callee } from "./interfaces/IUniswapV2Callee.sol";

/// @title SlippageAuction
/// @notice Slippage auction to sell tokens over time. Created via factory.
/// @dev Both tokens must be 18 decimals.
/// @dev https://github.com/FraxFinance/frax-bonds
contract SlippageAuction is ReentrancyGuard, Timelock2Step {
    using SafeERC20 for IERC20;

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

    /// @notice The name of this auction
    string public name;

    /// @notice Slippage precision
    uint256 public constant PRECISION = 1e18;

    /// @notice Stored information about details
    Detail[] public details;

    /// @notice The token used for buying the tokenSell
    address public immutable TOKEN_BUY;

    /// @notice The token being auctioned off
    address public immutable TOKEN_SELL;

    /// @notice Alias for TOKEN_BUY
    /// @dev Maintains UniswapV2 interface
    address public immutable token0;

    /// @notice Alias for TOKEN_SELL
    /// @notice Maintains UniswapV2 interface
    address public immutable token1;

    // ==============================================================================
    // Structs
    // ==============================================================================

    /// @notice Detail information behind an auction
    /// @notice Auction information
    /// @param amountListed Amount of sellToken placed for auction
    /// @param amountLeft Amount of sellToken remaining to buy
    /// @param amountExcessBuy Amount of any additional TOKEN_BUY sent to contract during auction
    /// @param amountExcessSell Amount of any additional TOKEN_SELL sent to contract during auction
    /// @param tokenBuyReceived Amount of tokenBuy that came in from sales
    /// @param priceLast Price of the last sale, in tokenBuy amount per tokenSell (amount of tokenBuy to purchase 1e18 tokenSell)
    /// @param priceMin Minimum price of 1e18 tokenSell, in tokenBuy
    /// @param priceDecay Price decay, (wei per second), using PRECISION
    /// @param priceSlippage Slippage fraction. E.g (0.01 * PRECISION) = 1%
    /// @param lastBuyTime Time of the last sale
    /// @param expiry UNIX timestamp when the auction ends
    /// @param active If the auction is active
    struct Detail {
        uint128 amountListed;
        uint128 amountLeft;
        uint128 amountExcessBuy;
        uint128 amountExcessSell;
        uint128 tokenBuyReceived;
        uint128 priceLast;
        uint128 priceMin;
        uint64 priceDecay;
        uint64 priceSlippage;
        uint32 lastBuyTime;
        uint32 expiry;
        bool active;
    }

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

    /// @param _timelock Address of the timelock/owner
    /// @param _tokenBuy Token used to purchase _tokenSell
    /// @param _tokenSell Token sold in the auction
    constructor(address _timelock, address _tokenBuy, address _tokenSell) Timelock2Step(_timelock) {
        name = string(abi.encodePacked("SlippageAuction: ", IERC20Metadata(_tokenSell).symbol()));
        TOKEN_BUY = _tokenBuy;
        TOKEN_SELL = _tokenSell;

        token0 = _tokenBuy;
        token1 = _tokenSell;
    }

    // ==============================================================================
    // Views
    // ==============================================================================

    /// @notice Returns the semantic version of this contract
    /// @return _major The major version
    /// @return _minor The minor version
    /// @return _patch The patch version
    function version() external pure returns (uint256 _major, uint256 _minor, uint256 _patch) {
        return (1, 0, 1);
    }

    /// @notice Calculates the pre-slippage price (with the user supplied auction _detail) from the time decay alone
    /// @param _detail The auction struct
    /// @return _price The price
    function getPreSlippagePrice(Detail memory _detail) public view returns (uint256 _price) {
        // Calculate Decay
        uint256 _decay = (_detail.priceDecay * (block.timestamp - _detail.lastBuyTime));

        // Calculate the sale price (in tokenBuy per tokenSell), factoring in the time decay
        if (_detail.priceLast < _decay) {
            return _price = _detail.priceMin;
        } else {
            _price = _detail.priceLast - _decay;
        }

        // Never go below the minimum price
        if (_price < _detail.priceMin) _price = _detail.priceMin;
    }

    /// @notice Calculates the pre-slippage price (with the current auction) from the time decay alone
    function getPreSlippagePrice() external view returns (uint256) {
        return getPreSlippagePrice(details[details.length - 1]);
    }

    /// @notice Calculates the amount of tokenSells out for a given tokenBuy amount
    /// @param amountIn Amount of tokenBuy in
    /// @param _revertOnOverAmountLeft Whether to revert if amountOut > amountLeft
    /// @return amountOut Amount of tokenSell out
    /// @return _slippagePerTokenSell The slippage component of the price change (in tokenBuy per tokenSell)
    /// @return _postPriceSlippage The post-slippage price from the time decay + slippage
    function getAmountOut(
        uint256 amountIn,
        bool _revertOnOverAmountLeft
    ) public view returns (uint256 amountOut, uint256 _slippagePerTokenSell, uint256 _postPriceSlippage) {
        // Get the auction number
        uint256 _auctionNumber = details.length - 1;

        // Get the auction info
        Detail memory _detail = details[_auctionNumber];

        // Revert if the auction is inactive or expired
        if (!_detail.active) revert AuctionNotActive();
        if (block.timestamp >= _detail.expiry) revert AuctionExpired();

        // Calculate the sale price (in tokenBuy per tokenSell), factoring in the time decay
        uint256 _preSlippagePrice = getPreSlippagePrice({ _detail: _detail });

        // Calculate the slippage component of the price (in tokenBuy per tokenSell)
        _slippagePerTokenSell = (_detail.priceSlippage * amountIn) / PRECISION;

        // Calculate the output amount of tokenSell
        amountOut = (amountIn * PRECISION) / (_preSlippagePrice + _slippagePerTokenSell);

        // Make sure you are not going over the amountLeft
        if (amountOut > _detail.amountLeft) {
            if (_revertOnOverAmountLeft) revert InsufficientTokenSellsAvailable();
            else amountOut = _detail.amountLeft;
        }

        // Set return value
        _postPriceSlippage = _preSlippagePrice + (2 * _slippagePerTokenSell); // Price impact is twice the slippage
    }

    /// @notice Calculates how much tokenBuy you would need to buy out the remaining tokenSell in the auction
    /// @return amountIn Amount of tokenBuy needed
    /// @return _slippagePerTokenSell The slippage component of the price change (in tokenBuy per tokenSell)
    /// @return _postPriceSlippage The post-slippage price from the time decay + slippage
    function getAmountInMax()
        external
        view
        returns (uint256 amountIn, uint256 _slippagePerTokenSell, uint256 _postPriceSlippage)
    {
        // Get the auction number
        uint256 _auctionNumber = details.length - 1;

        // Get the auction info
        Detail memory detail = details[_auctionNumber];

        // Call the internal function with amountLeft
        return _getAmountIn({ _detail: detail, amountOut: detail.amountLeft });
    }

    /// @notice Calculates how much tokenBuy you would need in order to obtain a given number of tokenSell
    /// @param amountOut The desired amount of tokenSell
    /// @return amountIn Amount of tokenBuy needed
    /// @return _slippagePerTokenSell The slippage component of the price change (in tokenBuy per tokenSell)
    /// @return _postPriceSlippage The post-slippage price from the time decay + slippage
    function getAmountIn(
        uint256 amountOut
    ) public view returns (uint256 amountIn, uint256 _slippagePerTokenSell, uint256 _postPriceSlippage) {
        // Get the auction number
        uint256 _auctionNumber = details.length - 1;

        // Get the auction info
        Detail memory detail = details[_auctionNumber];

        // Call the internal function with amountOut, set return values
        (amountIn, _slippagePerTokenSell, _postPriceSlippage) = _getAmountIn({ _detail: detail, amountOut: amountOut });
    }

    /// @notice Calculate how much tokenBuy you would need to obtain a given number of tokenSell
    /// @param _detail The auction struct
    /// @return amountIn Amount of tokenBuy needed
    /// @return _slippagePerTokenSell The slippage component of the price change (in tokenBuy per tokenSell)
    /// @return _postPriceSlippage The post-slippage price from the time decay + slippage
    function _getAmountIn(
        Detail memory _detail,
        uint256 amountOut
    ) internal view returns (uint256 amountIn, uint256 _slippagePerTokenSell, uint256 _postPriceSlippage) {
        // Do checks
        if (!_detail.active) revert AuctionNotActive();
        if (block.timestamp >= _detail.expiry) revert AuctionExpired();
        if (amountOut > _detail.amountLeft) revert InsufficientTokenSellsAvailable();

        // Calculate the sale price (in tokenBuy per tokenSell), factoring in the time decay
        uint256 _preSlippagePrice = getPreSlippagePrice({ _detail: _detail });

        // Math in a more readable format:
        // uint256 _numerator = (amountOut * _preSlippagePrice) / PRECISION;
        // uint256 _denominator = (PRECISION -
        //     ((amountOut * uint256(_detail.priceSlippage)) / PRECISION));
        // amountIn = (_numerator * PRECISION) / _denominator;

        // Set return params amountIn
        amountIn =
            (amountOut * _preSlippagePrice) /
            (PRECISION - (amountOut * uint256(_detail.priceSlippage)) / PRECISION);

        // Set return params, calculate the slippage component of the price (in tokenBuy per tokenSell)
        _slippagePerTokenSell = (_detail.priceSlippage * amountIn) / PRECISION;
        _postPriceSlippage = _preSlippagePrice + (2 * _slippagePerTokenSell); // Price impact is twice the slippage
    }

    /// @notice Calculates how much tokenBuy you would need in order to obtain a given number of tokenSell
    /// @dev Maintains compatibility with some router implementations
    /// @param amountOut The amount out of sell tokens
    /// @param tokenOut The sell token address
    /// @return amountIn The amount of tokenBuy needed
    function getAmountIn(uint256 amountOut, address tokenOut) public view returns (uint256 amountIn) {
        if (tokenOut != TOKEN_SELL) revert InvalidTokenOut();
        (amountIn, , ) = getAmountIn({ amountOut: amountOut });
    }

    /// @notice Calculates the amount of tokenSell out for a given tokenBuy amount
    /// @dev Used to maintain compatibility
    /// @param amountIn Amount of tokenBuy in
    /// @param tokenIn The token being swapped in
    /// @return amountOut Amount of tokenSells out
    function getAmountOut(uint256 amountIn, address tokenIn) public view returns (uint256 amountOut) {
        if (tokenIn != TOKEN_BUY) revert InvalidTokenIn();
        (amountOut, , ) = getAmountOut({ amountIn: amountIn, _revertOnOverAmountLeft: false });
    }

    /// @dev Uni v2 support without revert
    function skim(address) external pure {
        return;
    }

    /// @dev Uni v2 support without revert
    function sync() external pure {
        return;
    }

    function getAmountOut(uint256, uint256, uint256) external pure returns (uint256) {
        revert NotImplemented();
    }

    function getAmountIn(uint256, uint256, uint256) external pure returns (uint256) {
        revert NotImplemented();
    }

    function getReserves() external pure returns (uint112, uint112, uint32) {
        revert NotImplemented();
    }

    function price0CumulativeLast() external pure returns (uint256) {
        revert NotImplemented();
    }

    function price1CumulativeLast() external pure returns (uint256) {
        revert NotImplemented();
    }

    function kLast() external pure returns (uint256) {
        revert NotImplemented();
    }

    function factory() external pure returns (address) {
        revert NotImplemented();
    }

    function MINIMUM_LIQUIDITY() external pure returns (uint256) {
        revert NotImplemented();
    }

    function initialize(address, address) external pure {
        revert NotImplemented();
    }

    /// @notice Gets a struct instead of a tuple for details()
    /// @param _auctionNumber Detail ID
    /// @return The struct of the auction
    function getDetailStruct(uint256 _auctionNumber) external view returns (Detail memory) {
        return details[_auctionNumber];
    }

    /// @notice Returns the length of the details array
    /// @return _length The length of the details array
    function detailsLength() external view returns (uint256 _length) {
        _length = details.length;
    }

    /// @notice Returns the latest auction
    /// @dev Returns an empty struct if there are no auctions
    /// @return _latestAuction The latest auction struct
    function getLatestAuction() external view returns (Detail memory _latestAuction) {
        uint256 _length = details.length;
        if (_length == 0) return _latestAuction;
        _latestAuction = details[details.length - 1];
    }

    // ==============================================================================
    // Owner-only Functions
    // ==============================================================================

    /// @notice Parameters for starting an auction
    /// @dev Sender must have an allowance on tokenSell
    /// @param amountListed Amount of tokenSell being sold
    /// @param priceStart Starting price of 1e18 tokenSell, in tokenBuy
    /// @param priceMin Minimum price of 1e18 tokenSell, in tokenBuy
    /// @param priceDecay Price decay, (wei per second), using PRECISION
    /// @param priceSlippage Slippage fraction. E.g (0.01 * PRECISION) = 1%
    /// @param expiry UNIX timestamp when the auction ends
    struct StartAuctionParams {
        uint128 amountListed;
        uint128 priceStart;
        uint128 priceMin;
        uint64 priceDecay;
        uint64 priceSlippage;
        uint32 expiry;
    }

    /// @notice Starts a new auction
    /// @dev Requires an ERC20 allowance on the tokenSell prior to calling
    /// @param _params StartAuctionParams
    function startAuction(StartAuctionParams calldata _params) external nonReentrant returns (uint256 _auctionNumber) {
        _requireSenderIsTimelock();

        // Check expiry is not in the past
        if (_params.expiry < block.timestamp) revert Expired();

        // Ensure that enough amountListed are for sale to prevent round-down errors
        // see E2E test for 1e6 requirement.  At 1e8 requirement, there should be enough trades
        // to constitute an auction.
        if (_params.amountListed < 1e8) revert AmountListedTooLow();

        // Ensure that priceStart > priceMin
        if (_params.priceStart < _params.priceMin) revert PriceStartLessThanPriceMin();

        // Prevent edge-case revert of amountOut within getAmountOut
        if (_params.priceMin == 0 && _params.priceSlippage == 0) revert PriceMinAndSlippageBothZero();

        // Pre-compute the auction number
        _auctionNumber = details.length;

        // Ensure that the previous auction, if any, has been stopped
        if (_auctionNumber > 0) {
            Detail memory _lastAuction = details[_auctionNumber - 1];
            if (_lastAuction.active) revert LastAuctionStillActive();
        }

        // Create the auction
        details.push(
            Detail({
                amountListed: _params.amountListed,
                amountLeft: _params.amountListed,
                amountExcessBuy: 0,
                amountExcessSell: 0,
                tokenBuyReceived: 0,
                priceLast: _params.priceStart,
                priceMin: _params.priceMin,
                priceDecay: _params.priceDecay,
                priceSlippage: _params.priceSlippage,
                lastBuyTime: uint32(block.timestamp),
                expiry: _params.expiry,
                active: true
            })
        );

        emit AuctionStarted({
            auctionNumber: _auctionNumber,
            amountListed: _params.amountListed,
            priceStart: _params.priceStart,
            priceMin: _params.priceMin,
            priceDecay: _params.priceDecay,
            priceSlippage: _params.priceSlippage,
            expiry: _params.expiry
        });

        // Clear out any tokens held by the auction so that bookkeeping is accurate
        _withdrawAnyAvailableTokens({ _excess: true });

        // Take the tokenSells from the sender
        IERC20(TOKEN_SELL).safeTransferFrom(msg.sender, address(this), _params.amountListed);
    }

    /// @notice Ends the auction
    /// @dev Only callable by the auction owner
    /// @return tokenBuyReceived Amount of tokenBuy obtained from the auction
    /// @return tokenSellRemaining Amount of unsold tokenSell left over
    function stopAuction() public nonReentrant returns (uint256 tokenBuyReceived, uint256 tokenSellRemaining) {
        _requireSenderIsTimelock();

        // Get the auction info and perform checks
        uint256 _auctionNumber = details.length - 1;
        Detail storage detail = details[_auctionNumber];
        if (!detail.active) revert AuctionNotActive();

        // Skim excess token to sender if additional has been received to keep bookkeeping accurate
        _withdrawIfTokenBalance({ _token: TOKEN_BUY, _priorBalance: detail.tokenBuyReceived, _excess: true });
        _withdrawIfTokenBalance({ _token: TOKEN_SELL, _priorBalance: detail.amountLeft, _excess: true });

        // Set Return params
        tokenBuyReceived = IERC20(TOKEN_BUY).balanceOf(address(this));
        tokenSellRemaining = IERC20(TOKEN_SELL).balanceOf(address(this));

        // Effects: Update state with final balances;
        detail.active = false;
        detail.tokenBuyReceived = uint128(tokenBuyReceived);
        detail.amountLeft = uint128(tokenSellRemaining);

        // Return any TOKEN_BUY and TOKEN_SELL from the auction to the timelock
        _withdrawAnyAvailableTokens({ _excess: false });

        emit AuctionStopped({
            auctionNumber: _auctionNumber,
            tokenBuyReceived: tokenBuyReceived,
            tokenSellRemaining: tokenSellRemaining
        });
    }

    // ==============================================================================
    // Public Functions
    // ==============================================================================

    /// @notice Swaps tokenBuys for tokenSells
    /// @dev This low-level function should be called from a contract which performs important safety checks
    /// @dev Token0 is always the TOKEN_BUY, token1 is always the TOKEN_SELL
    /// @dev Maintains uniV2 interface
    /// @param amount0Out The amount of tokenBuys to receive
    /// @param amount1Out The amount of tokenSells to receive
    /// @param to The recipient of the output tokens
    /// @param data Callback data
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes memory data) public nonReentrant {
        if (amount0Out != 0) revert ExcessiveTokenBuyOut({ minOut: 0, actualOut: amount0Out });
        if (amount1Out == 0) revert InsufficientOutputAmount({ minOut: 1, actualOut: 0 });

        // Get the auction info (similar to get reserves in univ2)
        uint256 _auctionNumber = details.length - 1;
        Detail memory detail = details[_auctionNumber];

        // Transfer tokens
        IERC20(TOKEN_SELL).safeTransfer(to, amount1Out);

        // Callback if necessary for flash swap
        if (data.length > 0) {
            IUniswapV2Callee(to).uniswapV2Call({
                sender: msg.sender,
                amount0: amount0Out,
                amount1: amount1Out,
                data: data
            });
        }

        // Calculate the amount of tokenBuys in
        uint256 _tokenBuyBalance = IERC20(TOKEN_BUY).balanceOf(address(this));
        uint256 _tokenBuyIn = _tokenBuyBalance - detail.tokenBuyReceived;

        // Adheres to uniswap v2 interface, called here to prevent stack-too-deep error
        emit Swap({
            sender: msg.sender,
            amount0In: _tokenBuyIn,
            amount1In: 0,
            amount0Out: 0,
            amount1Out: amount1Out,
            to: to
        });

        // Call the internal function with amountOut
        (uint256 _minAmountIn, uint256 _slippagePerTokenSell, uint256 _postPriceSlippage) = _getAmountIn({
            _detail: detail,
            amountOut: amount1Out
        });

        // Check invariants
        if (_tokenBuyIn < _minAmountIn) revert InsufficientInputAmount({ minIn: _minAmountIn, actualIn: _tokenBuyIn });
        if (_minAmountIn == 0) revert InputAmountZero();

        // Mutate _auction, which has the previous state
        detail.amountLeft -= safeUint128(amount1Out);
        detail.tokenBuyReceived = safeUint128(_tokenBuyBalance);
        detail.priceLast = safeUint128(_postPriceSlippage);
        detail.lastBuyTime = uint32(block.timestamp);

        // Write back to state, similar to _update in univ2
        details[_auctionNumber] = detail;

        // Emit Buy event
        emit Buy({
            auctionNumber: _auctionNumber,
            tokenBuy: TOKEN_BUY,
            tokenSell: TOKEN_SELL,
            amountIn: safeUint128(_tokenBuyIn),
            amountOut: safeUint128(amount1Out),
            priceLast: detail.priceLast,
            slippagePerTokenSell: safeUint128(_slippagePerTokenSell)
        });
    }

    /// @notice Swaps an exact amount of input tokens for as many output tokens as possible
    /// @dev Must have an allowance on the TOKEN_BUY prior to invocation
    /// @dev Maintains uniV2 interface
    /// @param amountIn The amount of buy tokens to send.
    /// @param amountOutMin The minimum amount of sell tokens that must be received for the transaction not to revert
    /// @param to Recipient of the output tokens
    /// @param deadline Unix timestamp after which the transaction will revert
    /// @return _amounts The input token amount and output token amount
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory _amounts) {
        path; // compile warnings

        // Ensure deadline has not passed
        if (block.timestamp > deadline) revert Expired();

        // Calculate the amount of tokenSells out & check invariant
        (uint256 amountOut, , ) = getAmountOut({ amountIn: amountIn, _revertOnOverAmountLeft: true });
        if (amountOut < amountOutMin) {
            revert InsufficientOutputAmount({ minOut: amountOutMin, actualOut: amountOut });
        }
        // Interactions: Transfer tokenBuys to the contract
        IERC20(TOKEN_BUY).safeTransferFrom(msg.sender, address(this), amountIn);

        // Call the swap function
        swap({ amount0Out: 0, amount1Out: amountOut, to: to, data: new bytes(0) });

        // Set return values
        _amounts = new uint256[](2);
        _amounts[0] = amountIn;
        _amounts[1] = amountOut;
    }

    /// @notice Receives an exact amount of output tokens for as few input tokens as possible
    /// @dev Must have an allowance on the TOKEN_BUY prior to invocation
    /// @dev Maintains uniV2 interface
    /// @param amountOut The amount of sell tokens to receive
    /// @param amountInMax The maximum amount of buy tokens that can be required before the transaction reverts
    /// @param to Recipient of the output tokens
    /// @param deadline Unix timestamp after which the transaction will revert
    /// @return _amounts The input token amount and output token amount
    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory _amounts) {
        path; // compile warning

        // Ensure deadline has not passed
        if (block.timestamp > deadline) revert Expired();

        // Calculate the amount of tokenBuys in & check invariant
        (uint256 amountIn, , ) = getAmountIn({ amountOut: amountOut });
        if (amountIn > amountInMax) revert ExcessiveInputAmount({ minIn: amountInMax, actualIn: amountIn });

        // Interactions: Transfer tokenBuys to the contract
        IERC20(TOKEN_BUY).safeTransferFrom(msg.sender, address(this), amountIn);

        // Call the swap function
        swap({ amount0Out: 0, amount1Out: amountOut, to: to, data: new bytes(0) });

        // Set return variable
        _amounts = new uint256[](2);
        _amounts[0] = amountIn;
        _amounts[1] = amountOut;
    }

    // ==============================================================================
    // Helpers
    // ==============================================================================

    /// @notice Withdraw available TOKEN_BUY and TOKEN_SELL on startAuction() and stopAuction()
    /// @param _excess Whether to bookkeep any excess tokens received outside of auction
    function _withdrawAnyAvailableTokens(bool _excess) private {
        _withdrawIfTokenBalance({ _token: TOKEN_BUY, _priorBalance: 0, _excess: _excess });
        _withdrawIfTokenBalance({ _token: TOKEN_SELL, _priorBalance: 0, _excess: _excess });
    }

    /// @notice Withdraw available TOKEN_BUY and TOKEN_SELL on startAuction() and stopAuction()
    /// @param _token Address of the token you want to withdraw
    /// @param _priorBalance Prior balance of the _token
    /// @param _excess Whether to bookkeep any excess tokens received outside of auction
    function _withdrawIfTokenBalance(address _token, uint256 _priorBalance, bool _excess) private {
        // Fetch the current balance of _token
        uint256 balance = IERC20(_token).balanceOf(address(this));

        // If the current balance is higher than the prior balance
        if (balance > _priorBalance) {
            uint256 amount = balance - _priorBalance;

            // Bookkeep any excess token received
            if (_excess) {
                Detail storage detail = details[details.length - 1];
                if (_token == TOKEN_BUY) {
                    detail.amountExcessBuy += safeUint128(amount);
                } else {
                    // token == TOKEN_SELL
                    detail.amountExcessSell += safeUint128(amount);
                }
            }

            IERC20(_token).safeTransfer(msg.sender, amount);
        }
    }

    /// @dev Overflow protection
    function safeUint128(uint256 number) internal pure returns (uint128 casted) {
        if (number > type(uint128).max) revert Overflow();
        casted = uint128(number);
    }

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

    /// @notice Emitted when a user attempts to start an auction selling too few tokens
    error AmountListedTooLow();

    /// @notice Emitted when a user attempts to end an auction that has been stopped
    error AuctionNotActive();

    /// @notice Emitted when a user attempts to interact with an auction that has expired
    error AuctionExpired();

    /// @notice Emitted when a user attempts to start a new auction before the previous one has been stopped (via ```stopAuction()```)
    error LastAuctionStillActive();

    /// @notice Emitted when a user attempts to swap a given amount of buy tokens that would result in an insufficient amount of sell tokens
    /// @param minOut Minimum out that the user expects
    /// @param actualOut Actual amount out that would occur
    error InsufficientOutputAmount(uint256 minOut, uint256 actualOut);

    /// @notice Emitted when a user attempts to swap an insufficient amount of buy tokens
    /// @param minIn Minimum in that the contract requires
    /// @param actualIn Actual amount in that has been deposited
    error InsufficientInputAmount(uint256 minIn, uint256 actualIn);

    /// @notice Emitted when a user attempts to swap an excessive amount of buy tokens for aa given amount of sell tokens
    /// @param minIn    Minimum in that the user expects
    /// @param actualIn Actual amount in that would occur
    error ExcessiveInputAmount(uint256 minIn, uint256 actualIn);

    /// @notice Emitted when a user attempts to buy more sell tokens than are left in the auction
    error InsufficientTokenSellsAvailable();

    /// @notice Emitted when attempting to swap where the calculated amountIn is 0
    error InputAmountZero();

    /// @notice Emitted when a user attempts to buy the tokenBuy using the swap() function
    error ExcessiveTokenBuyOut(uint256 minOut, uint256 actualOut);

    /// @notice Emitted when a user attempts to make a swap after the transaction deadline has passed
    error Expired();

    /// @notice Emitted when a user attempts to use an invalid buy token
    error InvalidTokenIn();

    /// @notice Emitted when a user attempts to use an invalid sell token
    error InvalidTokenOut();

    /// @notice Emitted when calling `startAuction()` when `StartAuctionParams.priceMin == 0 && StartAuctionParams.priceSlippage == 0`
    error PriceMinAndSlippageBothZero();

    /// @notice Emitted when attempting to call a uni-v2 pair function that is not supported by this contract
    error NotImplemented();

    /// @notice Emitted when downcasting a uint on type overflow
    error Overflow();

    /// @notice Emitted when a user attempts to start an auction with `_params.priceStart < _params.priceMin`
    error PriceStartLessThanPriceMin();

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

    /// @dev Emitted when an auction is stopped
    /// @param auctionNumber The ID of the auction
    /// @param tokenBuyReceived Amount of tokenBuy obtained from the auction
    /// @param tokenSellRemaining Amount of unsold tokenSells left over
    event AuctionStopped(uint256 auctionNumber, uint256 tokenBuyReceived, uint256 tokenSellRemaining);

    /// @dev Emitted when a swap occurs and has more information than the ```Swap``` event
    /// @param auctionNumber The ID of the auction, and index in the details array
    /// @param tokenBuy The token used to buy the tokenSell being auctioned off
    /// @param tokenSell The token being auctioned off
    /// @param amountIn Amount of tokenBuy in
    /// @param amountOut Amount of tokenSell out
    /// @param priceLast The execution price of the buy
    /// @param slippagePerTokenSell How many tokenBuys (per tokenSell) were added as slippage
    event Buy(
        uint256 auctionNumber,
        address tokenBuy,
        address tokenSell,
        uint128 amountIn,
        uint128 amountOut,
        uint128 priceLast,
        uint128 slippagePerTokenSell
    );

    /// @notice Emitted when a swap occurs
    /// @param sender The address of the sender
    /// @param amount0In The amount of TOKEN_BUY in
    /// @param amount1In The amount of TOKEN_SELL in
    /// @param amount0Out The amount of TOKEN_BUY out
    /// @param amount1Out The amount of TOKEN_SELL out
    /// @param to The address of the recipient
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );

    /// @dev Emitted when an auction is started
    /// @param auctionNumber The ID of the auction
    /// @param amountListed Amount of tokenSell being sold
    /// @param priceStart Starting price of the tokenSell, in tokenBuy
    /// @param priceMin Minimum price of the tokenSell, in tokenBuy
    /// @param priceDecay Price decay, per day, using PRECISION
    /// @param priceSlippage Slippage fraction. E.g (0.01 * PRECISION) = 1%
    /// @param expiry Expiration time of the auction
    event AuctionStarted(
        uint256 auctionNumber,
        uint128 amountListed,
        uint128 priceStart,
        uint128 priceMin,
        uint128 priceDecay,
        uint128 priceSlippage,
        uint32 expiry
    );
}

// File: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 ReentrancyGuard {
    // 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 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    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
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // 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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/utils/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: lib/frax-standard-solidity/src/access-control/v2/Timelock2Step.sol



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

// ====================================================================
// |     ______                   _______                             |
// |    / _____________ __  __   / ____(_____  ____ _____  ________   |
// |   / /_  / ___/ __ `| |/_/  / /_  / / __ \/ __ `/ __ \/ ___/ _ \  |
// |  / __/ / /  / /_/ _>  <   / __/ / / / / / /_/ / / / / /__/  __/  |
// | /_/   /_/   \__,_/_/|_|  /_/   /_/_/ /_/\__,_/_/ /_/\___/\___/   |
// |                                                                  |
// ====================================================================
// ========================== 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 _timelock) {
        timelockAddress = _timelock;
    }

    // ============================================================================================
    // 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: src/contracts/interfaces/IUniswapV2Callee.sol



// SPDX-License-Identifier: ISC
pragma solidity ^0.8.23;

interface IUniswapV2Callee {
    function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external;
}

// File: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (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;
    }
}

// File: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/interfaces/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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/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: lib/frax-standard-solidity/lib/openzeppelin-contracts/contracts/utils/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();
        }
    }
}