// Contract: BlacklistAndTimeFilter
// Target function: function withdrawAll() external nonReentrant
8).
     *
     * Counterpart to Solidity's `uint8` operator.
     *
     * Requirements:
     *
     * - input must fit into 8 bits.
     */
    function toUint8(uint256 value) internal pure returns (uint8) {
        require(value < 2**8, "SafeCast: value doesn\'t fit in 8 bits");
        return uint8(value);
    }

    /**
     * @dev Converts a signed int256 into an unsigned uint256.
     *
     * Requirements:
     *
     * - input must be greater than or equal to 0.
     */
    function toUint256(int256 value) internal pure returns (uint256) {
        require(value >= 0, "SafeCast: value must be positive");
        return uint256(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
     *
     * _Available since v3.1._
     */
    function toInt128(int256 value) internal pure returns (int128) {
        require(value >= -2**127 && value < 2**127, "SafeCast: value doesn\'t fit in 128 bits");
        return int128(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
     *
     * _Available since v3.1._
     */
    function toInt64(int256 value) internal pure returns (int64) {
        require(value >= -2**63 && value < 2**63, "SafeCast: value doesn\'t fit in 64 bits");
        return int64(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
     *
     * _Available since v3.1._
     */
    function toInt32(int256 value) internal pure returns (int32) {
        require(value >= -2**31 && value < 2**31, "SafeCast: value doesn\'t fit in 32 bits");
        return int32(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
     *
     * _Available since v3.1._
     */
    function toInt16(int256 value) internal pure returns (int16) {
        require(value >= -2**15 && value < 2**15, "SafeCast: value doesn\'t fit in 16 bits");
        return int16(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.
     *
     * _Available since v3.1._
     */
    function toInt8(int256 value) internal pure returns (int8) {
        require(value >= -2**7 && value < 2**7, "SafeCast: value doesn\'t fit in 8 bits");
        return int8(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) {
        require(value < 2**255, "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }
}

library SafeAggregatorInterface {
    using SafeCast for int256;

    uint256 constant ONE_DAY_IN_SECONDS = 86400;

    function safeUnsignedLatest(AggregatorV3Interface oracle) internal view returns (uint256) {
        (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) = oracle.latestRoundData();
        require((roundId==answeredInRound) && (updatedAt+ONE_DAY_IN_SECONDS > block.timestamp), "Oracle out of date");
        return answer.toUint256();
    }
}

/*
    This exchange interface implements the Matcha PLP API

    Also controls swapFee and approvalContract (to minimize gas)

    It must be created before the Pool contract
    because it gets passed to the Pool contract constructor.

    Then setPoolAddress should be called to link this contract and destroy ownership.
*/
contract ClipperExchangeInterface is ReentrancyGuard, Ownable {
    using Sqrt for uint256;
    using UniERC20 for ERC20;
    using SafeAggregatorInterface for AggregatorV3Interface;

    ClipperPool public theExchange;
    ApprovalInterface public approvalContract;

    uint256 public swapFee;
    uint256 constant MAXIMUM_SWAP_FEE = 500;
    uint256 constant ONE_IN_DEFAULT_DECIMALS_DIVIDED_BY_ONE_HUNDRED_SQUARED = 1e14;
    uint256 constant ONE_IN_TEN_DECIMALS = 1e10;
    uint256 constant ONE_HUNDRED_PERCENT_IN_BPS = 1e4;
    uint256 constant ONE_BASIS_POINT_IN_TEN_DECIMALS = 1e6;

    address constant MATCHA_ETH_SIGIL = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
    address constant CLIPPER_ETH_SIGIL = address(0);
    address immutable myAddress;

    event Swapped(
        address inAsset,
        address outAsset,
        address recipient,
        uint256 inAmount,
        uint256 outAmount,
        bytes auxiliaryData
    );

    event SwapFeeModified(
        uint256 swapFee
    );

    modifier poolOwnerOnly() {
        require(msg.sender == theExchange.owner(), "Clipper: Only owner");
        _;
    }


    constructor(ApprovalInterface initialApprovalContract, uint256 initialSwapFee) {
        require(initialSwapFee < MAXIMUM_SWAP_FEE, "Clipper: Maximum swap fee exceeded");
        approvalContract = initialApprovalContract;
        swapFee = initialSwapFee;
        myAddress = address(this);
    }

    // This function should be called immediately after the pool is initialzied
    // It can only be called once because of renouncing ownership
    function setPoolAddress(address payable poolAddress) external onlyOwner {
        theExchange = ClipperPool(poolAddress);
        renounceOwnership();
    }

    function modifyApprovalContract(ApprovalInterface newApprovalContract) external poolOwnerOnly {
        approvalContract = newApprovalContract;
    }

    function modifySwapFee(uint256 newSwapFee) external poolOwnerOnly {
        require(newSwapFee < MAXIMUM_SWAP_FEE, "Clipper: Maximum swap fee exceeded");
        swapFee = newSwapFee;
        emit SwapFeeModified(newSwapFee);
    }

    // Used for deposits and withdrawals, but not swaps
    function invariant() public view returns (uint256) {
        (uint256 balance, uint256 M, uint256 marketWeight) = theExchange.findBalanceAndMultiplier(ERC20(CLIPPER_ETH_SIGIL));
        uint256 cumulant = (M*balance).sqrt()/marketWeight;
        uint i;
        uint n = theExchange.nTokens();
        while(i < n){
            ERC20 the_token = ERC20(theExchange.tokenAt(i));
            (balance, M, marketWeight) = theExchange.findBalanceAndMultiplier(the_token);
            cumulant = cumulant + (M*balance).sqrt()/marketWeight;
            i++;
        }
        // Divide to put everything on a 1e18 track...
        return (cumulant*cumulant)/ONE_IN_DEFAULT_DECIMALS_DIVIDED_BY_ONE_HUNDRED_SQUARED;
    }

    // Closed-form invariant swap expression
    // solves: (sqrt(Mx)/X + sqrt(Ny)/Y) == (sqrt(M(x+a)/X) + sqrt(N(y-b))/Y) for b
    function invariantSwap(uint256 x, uint256 y, uint256 M, uint256 N, uint256 a, uint256 marketWeightX, uint256 marketWeightY) internal pure returns(uint256) {
        uint256 Ma = M*a;
        uint256 Mx = M*x;
        uint256 rMax = (Ma+Mx).sqrt();
        // Since rMax >= rMx, we can start with a great guess
        uint256 rMx = Mx.sqrt(rMax+1);
        uint256 rNy = (N*y).sqrt();
        uint256 X2 = marketWeightX*marketWeightX;
        uint256 XY = marketWeightX*marketWeightY;
        uint256 Y2 = marketWeightY*marketWeightY;

        // multiply by X*Y to get: 
        if(rMax*marketWeightY >= (rNy*marketWeightX+rMx*marketWeightY)) {
            return y;
        } else {
            return (2*((XY*rNy*(rMax-rMx)) + Y2*(rMx*rMax-Mx)) - Y2*Ma)/(N*X2);
        }
    }

    // For gas savings, we query the existing balance of the input token exactly once, which is why this function needs to return
    // both output AND input
    function calculateSwapAmount(ERC20 inputToken, ERC20 outputToken, uint256 totalInputToken) public view returns(uint256 outputAmount, uint256 inputAmount) {
        // balancesAndMultipliers checks for tradability
        (uint256 x, uint256 y, uint256 M, uint256 N, uint256 weightX, uint256 weightY) = theExchange.balancesAndMultipliers(inputToken, outputToken);
        inputAmount = totalInputToken-x;
        uint256 b = invariantSwap(x, y, M, N, inputAmount, weightX, weightY);
        // trader gets back b-swapFee*b/10000 (swapFee is in basis points)
        outputAmount = b-((b*swapFee)/10000);
    }

    // Swaps between input and output, where ERC20 can be ERC20 or pure ETH
    // emits a Swapped event
    function unifiedSwap(ERC20 _input, ERC20 _output, address recipient, uint256 totalInputToken, uint256 minBuyAmount, bytes calldata auxiliaryData) internal returns (uint256 boughtAmount) {
        require(address(this)==myAddress && approvalContract.approveSwap(recipient), "Clipper: Recipient not approved");
        uint256 inputTokenAmount;
        (boughtAmount, inputTokenAmount) = calculateSwapAmount(_input, _output, totalInputToken);
        require(boughtAmount >= minBuyAmount, "Clipper: Not enough output");
        
        theExchange.syncAndTransfer(_input, _output, recipient, boughtAmount);
        
        emit Swapped(address(_input), address(_output), recipient, inputTokenAmount, boughtAmount, auxiliaryData);
    }

    /* These next four functions are the Matcha PLP API */
    
    // Returns how much of the 'outputToken' would be returned if 'sellAmount'
    // of 'inputToken' was sold.
    function getSellQuote(address inputToken, address outputToken, uint256 sellAmount) external view returns (uint256 outputTokenAmount){
        ERC20 _input = ERC20(inputToken==MATCHA_ETH_SIGIL ? CLIPPER_ETH_SIGIL : inputToken);
        ERC20 _output = ERC20(outputToken==MATCHA_ETH_SIGIL ? CLIPPER_ETH_SIGIL : outputToken);
        (outputTokenAmount, ) = calculateSwapAmount(_input, _output, sellAmount+theExchange.lastBalance(_input));
    }

    function sellTokenForToken(address inputToken, address outputToken, address recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external returns (uint256 boughtAmount) {
        ERC20 _input = ERC20(inputToken);
        ERC20 _output = ERC20(outputToken);
        
        uint256 inputTokenAmount = _input.balanceOf(address(theExchange));
        boughtAmount = unifiedSwap(_input, _output, recipient, inputTokenAmount, minBuyAmount, auxiliaryData);
    }

    // Matcha allows for either ETH pre-deposit, or msg.value transfer. We support both.
    function sellEthForToken(address outputToken, address recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external payable returns (uint256 boughtAmount){
        ERC20 _input = ERC20(CLIPPER_ETH_SIGIL);
        ERC20 _output = ERC20(outputToken);
        // Will no-op if msg.value == 0
        _input.uniTransferFromSender(msg.value, address(theExchange));
        uint256 inputETHAmount = address(theExchange).balance;
        boughtAmount = unifiedSwap(_input, _output, recipient, inputETHAmount, minBuyAmount, auxiliaryData);
    }

    function sellTokenForEth(address inputToken, address payable recipient, uint256 minBuyAmount, bytes calldata auxiliaryData) external returns (uint256 boughtAmount){
        ERC20 _input = ERC20(inputToken);
        uint256 inputTokenAmount = _input.balanceOf(address(theExchange));
        boughtAmount = unifiedSwap(_input, ERC20(CLIPPER_ETH_SIGIL), recipient, inputTokenAmount, minBuyAmount, auxiliaryData);
    }


    // Allows a trader to convert their Pool token into a single pool asset
    // This is essentially a swap between the pool token and something else
    // Note that it is the responsibility of the trader to tender an offer that does not decrease the invariant
    function withdrawInto(uint256 amount, ERC20 outputToken, uint256 outputTokenAmount) external nonReentrant {
        require(theExchange.isTradable(outputToken) && outputTokenAmount > 0, "Clipper: Unsupported withdrawal");
        // Have to sync before calculating the invariant
        // Otherwise, we may run into issues if someone erroneously transferred this outputToken to us
        // Immediately before the withdraw call.
        theExchange.sync(outputToken);
        uint256 initialFullyDilutedSupply = theExchange.fullyDilutedSupply();
        uint256 beforeWithdrawalInvariant = invariant();

        // This will fail if the sender doesn't have enough
        theExchange.swapBurn(msg.sender, amount);
        
        // This will fail if we don't have enough
        // Also syncs automatically
        theExchange.transferAsset(outputToken, msg.sender, outputTokenAmount);
        // so the invariant will have changed....
        uint256 afterWithdrawalInvariant = invariant();

        // TOKEN FRACTION BURNED:
        // amount / initialFullyDilutedSupply
        // INVARIANT FRACTION BURNED:
        // (before-after) / before
        // TOKEN_FRACTION_BURNED >= INVARIANT_FRACTION_BURNED + FEE
        // where fee is swapFee basis points of TOKEN_FRACTION_BURNED

        uint256 tokenFractionBurned = (ONE_IN_TEN_DECIMALS*amount)/initialFullyDilutedSupply;
        uint256 invariantFractionBurned = (ONE_IN_TEN_DECIMALS*(beforeWithdrawalInvariant-afterWithdrawalInvariant))/beforeWithdrawalInvariant;
        uint256 feeFraction = (tokenFractionBurned*swapFee*ONE_BASIS_POINT_IN_TEN_DECIMALS)/ONE_IN_TEN_DECIMALS;
        require(tokenFractionBurned >= (invariantFractionBurned+feeFraction), "Too much taken");
        // This is essentially a swap between the pool token into the output token
        emit Swapped(address(theExchange), address(outputToken), msg.sender, amount, outputTokenAmount, "");
    }

    // myFraction is a ten-decimal fraction
    // theFee is in Basis Points
    function _withdraw(uint256 myFraction, uint256 theFee) internal {
        ERC20 the_token;
        uint256 toTransfer;
        uint256 fee;

        uint i;
        uint n = theExchange.nTokens();
        while(i < n) {
            the_token = ERC20(theExchange.tokenAt(i));
            toTransfer = (myFraction*the_token.uniBalanceOf(address(theExchange))) / ONE_IN_TEN_DECIMALS;
            fee = (toTransfer*theFee)/ONE_HUNDRED_PERCENT_IN_BPS;
            // syncs done automatically on transfer
            theExchange.transferAsset(the_token, msg.sender, toTransfer-fee);
            i++;
        }
        the_token = ERC20(CLIPPER_ETH_SIGIL);
        toTransfer = (myFraction*the_token.uniBalanceOf(address(theExchange))) / ONE_IN_TEN_DECIMALS;
        fee = (toTransfer*theFee)/ONE_HUNDRED_PERCENT_IN_BPS;
        // syncs done automatically on transfer
        theExchange.transferAsset(the_token, msg.sender, toTransfer-fee);
    }