contract UniswapMakerBroker is MakerBrokerBase {
  using UniswapFactoryHelper for IUniswapFactory;

  address public wethTokenAddress;
  address public loopringProtocol;
  IUniswapFactory public uniswapFactory;

  mapping(address => address) public tokenToExchange;
  mapping(address => bool) public tokenToIsSetup;

  constructor(address _loopringProtocol, address _uniswapFactory, address _wethTokenAddress) public {
    loopringProtocol = _loopringProtocol;
    wethTokenAddress = _wethTokenAddress;
    uniswapFactory = IUniswapFactory(_uniswapFactory);
  }

  function setupToken(address token, bool setupExchange) public {
    if (setupExchange) {
      IUniswapExchange exchange = uniswapFactory.exchangeOf(token);
      ERC20(token).approve(address(exchange), 10 ** 70);
      tokenToExchange[token] = address(exchange);
    }
    ERC20(token).approve(loopringProtocol, 10 ** 70);
    tokenToIsSetup[token] = true;
  }

  function () external payable {
    // No op, but accepts ETH being sent to this contract.
  }

  // --------------------------------
  // Loopring Broker Delegate

  function brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public returns (bool) {
    require(msg.sender == loopringProtocol, "Uniswap MakerBroker: Unauthorized caller");
    require(tokenToIsSetup[request.tokenS], "Uniswap MakerBroker: tokenS is not setup yet");

    for (uint i = 0; i < request.orders.length; i++) {
      require(request.orders[i].tokenRecipient == address(this), "Uniswap MakerBroker: Order tokenRecipient must be this broker");
      require(request.orders[i].owner == owner, "Uniswap MakerBroker: Order owner must be the owner of this contract");
    }

    if (request.tokenB == wethTokenAddress) {
      // We need to convert WETH to ETH to 1) avoid double fee payment on Uniswap and 2) get the best price on Uniswap
      WETH(wethTokenAddress).withdraw(request.totalFillAmountB);
    }

    // perform exchange
    bool isTokenBExchangeWrapper = abi.decode(request.orders[0].extraData, (bool));
    IUniswapExchange exchange;
    if (isTokenBExchangeWrapper) {
      exchange = IUniswapExchange(address(uint160(tokenToExchange[request.tokenB])));
    } else {
      exchange = IUniswapExchange(address(uint160(tokenToExchange[request.tokenS])));
    }

    uint deadline = block.timestamp + 1;
    uint exchangeAmount;
    if (request.tokenS == wethTokenAddress) {
      exchangeAmount = exchange.tokenToEthSwapInput(request.totalFillAmountB, request.totalRequestedAmountS, deadline);
    } else if (request.tokenB == wethTokenAddress) {
      exchangeAmount = exchange.ethToTokenSwapInput.value(request.totalFillAmountB)(request.totalRequestedAmountS, deadline);
    } else {
      // Pure token to token
      address tokenToBuy;
      if (isTokenBExchangeWrapper) {
        request.tokenS;
      } else {
        request.tokenB;
      }
      exchangeAmount = exchange.tokenToTokenSwapInput(
        request.totalFillAmountB,
        request.totalRequestedAmountS,
      /* minEthBoughtInWei */ 1, // We don't care about the token <-> ETH exchange rate as long as we get enough tokenB
        deadline,
        tokenToBuy);
    }

    if (request.tokenS == wethTokenAddress) {
      // Orders on Dolomite require we use WETH instead of ETH, so we must convert it back
      WETH(wethTokenAddress).deposit.value(exchangeAmount)();
    }

    return false;
  }

  function onOrderFillReport(BrokerData.BrokerInterceptorReport memory fillReport) public {
    // No op
  }

  /*
   * Balance is unknown as it is entirely dependent on the offer in Uniswap being filled.
   * Thus we send down a very large number to ensure the relay/matching engine believes
   * orders from this broker have enough of a balance for trades to be filled.
   */
  function brokerBalanceOf(address owner, address tokenAddress) public view returns (uint) {
    return 10 ** 70;
  }

}
/*
 * Inherits Loopring's IBrokerDelegate and sources liquidity from Uniswap
 * when the Loopring protocol requests a token approval. Because the Loopring
 * protocol expects the taker order to precede maker orders, and non-brokered
 * transfers occur before before brokered transfers, it is guaranteed that this
 * broker contract will receive the necessary tokens to trade, right before it
 * sets the approval and the Loopring protocol transfers the tokens out. Thus,
 * liquidity can be sourced on-chain with no money down!
 */
function brokerBalanceOf(address owner, address tokenAddress) public view returns (uint) {
  return 10 ** 70;
}

/*
 * Balance is unknown as it is entirely dependent on the offer in Uniswap being filled.
 * Thus we send down a very large number to ensure the relay/matching engine believes
 * orders from this broker have enough of a balance for trades to be filled.
 */