contract MixinUniswap is
    MixinAdapterAddresses
{
    using LibERC20TokenV06 for IERC20TokenV06;

    /// @dev Mainnet address of the WETH contract.
    IEtherTokenV06 private immutable WETH;
    /// @dev Mainnet address of the `UniswapExchangeFactory` contract.
    IUniswapExchangeFactory private immutable UNISWAP_EXCHANGE_FACTORY;

    constructor(AdapterAddresses memory addresses)
        public
    {
        WETH = IEtherTokenV06(addresses.weth);
        UNISWAP_EXCHANGE_FACTORY = IUniswapExchangeFactory(addresses.uniswapExchangeFactory);
    }

    function _tradeUniswap(
        IERC20TokenV06 buyToken,
        uint256 sellAmount,
        bytes memory bridgeData
    )
        internal
        returns (uint256 boughtAmount)
    {
        // Decode the bridge data to get the `sellToken`.
        (IERC20TokenV06 sellToken) = abi.decode(bridgeData, (IERC20TokenV06));

        // Get the exchange for the token pair.
        IUniswapExchange exchange = _getUniswapExchangeForTokenPair(
            sellToken,
            buyToken
        );

        // Convert from WETH to a token.
        if (sellToken == WETH) {
            // Unwrap the WETH.
            WETH.withdraw(sellAmount);
            // Buy as much of `buyToken` token with ETH as possible
            boughtAmount = exchange.ethToTokenTransferInput{ value: sellAmount }(
                // Minimum buy amount.
                1,
                // Expires after this block.
                block.timestamp,
                // Recipient is `this`.
                address(this)
            );

        // Convert from a token to WETH.
        } else if (buyToken == WETH) {
            // Grant the exchange an allowance.
            sellToken.approveIfBelow(
                address(exchange),
                sellAmount
            );
            // Buy as much ETH with `sellToken` token as possible.
            boughtAmount = exchange.tokenToEthSwapInput(
                // Sell all tokens we hold.
                sellAmount,
                // Minimum buy amount.
                1,
                // Expires after this block.
                block.timestamp
            );
            // Wrap the ETH.
            WETH.deposit{ value: boughtAmount }();
        // Convert from one token to another.
        } else {
            // Grant the exchange an allowance.
            sellToken.approveIfBelow(
                address(exchange),
                sellAmount
            );
            // Buy as much `buyToken` token with `sellToken` token
            boughtAmount = exchange.tokenToTokenSwapInput(
                // Sell all tokens we hold.
                sellAmount,
                // Minimum buy amount.
                1,
                // Must buy at least 1 intermediate wei of ETH.
                1,
                // Expires after this block.
                block.timestamp,
                // Convert to `buyToken`.
                buyToken
            );
        }

        return boughtAmount;
    }

    /// @dev Retrieves the uniswap exchange for a given token pair.
    ///      In the case of a WETH-token exchange, this will be the non-WETH token.
    ///      In th ecase of a token-token exchange, this will be the first token.
    /// @param sellToken The address of the token we are converting from.
    /// @param buyToken The address of the token we are converting to.
    /// @return exchange The uniswap exchange.
    function _getUniswapExchangeForTokenPair(
        IERC20TokenV06 sellToken,
        IERC20TokenV06 buyToken
    )
        private
        view
        returns (IUniswapExchange exchange)
    {
        // Whichever isn't WETH is the exchange token.
        exchange = sellToken == WETH
            ? UNISWAP_EXCHANGE_FACTORY.getExchange(buyToken)
            : UNISWAP_EXCHANGE_FACTORY.getExchange(sellToken);
        require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
    }
}
function _getUniswapExchangeForTokenPair(
    IERC20TokenV06 sellToken,
    IERC20TokenV06 buyToken
)
    private
    view
    returns (IUniswapExchange exchange)
{
    // Whichever isn't WETH is the exchange token.
    exchange = sellToken == WETH
        ? UNISWAP_EXCHANGE_FACTORY.getExchange(buyToken)
        : UNISWAP_EXCHANGE_FACTORY.getExchange(sellToken);
    require(address(exchange) != address(0), "NO_UNISWAP_EXCHANGE_FOR_TOKEN");
}

/// @dev Retrieves the uniswap exchange for a given token pair.
///      In the case of a WETH-token exchange, this will be the non-WETH token.
///      In th ecase of a token-token exchange, this will be the first token.
/// @param sellToken The address of the token we are converting from.
/// @param buyToken The address of the token we are converting to.
/// @return exchange The uniswap exchange.