contract OasisMakerBroker is MakerBrokerBase {

  Oasis public oasis;
  address public loopringProtocol;

  constructor(address _loopringProtocol, address _oasis) public {
    oasis = Oasis(_oasis);
    loopringProtocol = _loopringProtocol;
  }

  function setLoopringProtocol(address _loopringProtocol) external onlyOwner {
    loopringProtocol = _loopringProtocol;
  }

  function setOasis(address _oasis) external onlyOwner {
    oasis = Oasis(_oasis);
  }

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

  function enableToken(address token) external {
    ERC20(token).approve(address(loopringProtocol), 10 ** 70);
    ERC20(token).approve(address(oasis), 10 ** 70);
  }

  function enableTokens(address[] calldata tokens) external {
    for (uint i = 0; i < tokens.length; i++) {
      address token = tokens[i];
      ERC20(token).approve(address(loopringProtocol), 10 ** 70);
      ERC20(token).approve(address(oasis), 10 ** 70);
    }
  }

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

  function brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public returns (bool) {
    require(msg.sender == loopringProtocol, "Oasis MakerBroker: Unauthorized caller");
    require(request.totalRequestedFeeAmount == 0, "Oasis MakerBroker: Cannot be charged a fee");

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

      (uint offerId) = abi.decode(order.extraData, (uint));
      (bool success, bytes memory returnData) = address(oasis).call(
        abi.encodePacked(
          oasis.buy.selector,
          abi.encode(offerId, order.requestedAmountS)
        )
      );
      require(success, "Oasis MakerBroker: Oasis matching failed");
    }

    return false;
    // Does not use onOrderFillReport
  }

  function onOrderFillReport(BrokerData.BrokerInterceptorReport memory fillReport) public {
    // Do nothing
  }

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

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

  uint public constant PAGE_SIZE = 20;

  function getOrderBookRowCount(address tokenS, address tokenB) public view returns (uint) {
    return oasis.getOfferCount(ERC20(tokenS), ERC20(tokenB));
  }

  function getOrderBookPageCount(address tokenS, address tokenB) public view returns (uint) {
    uint numRows = getOrderBookRowCount(tokenS, tokenB);
    if (numRows % PAGE_SIZE == 0) return numRows / PAGE_SIZE;
    return (numRows / PAGE_SIZE) + 1;
  }

  function getOrderBookRows(address tokenS, address tokenB, uint pointer)
  public
  view
  returns (
    uint[] memory ids,
    uint[] memory sellAmounts,
    uint[] memory buyAmounts,
    bool[] memory isActives
  )
  {
    ERC20 sellToken = ERC20(tokenS);
    ERC20 buyToken = ERC20(tokenB);

    uint lastOfferId = pointer == 0
    ? oasis.getBestOffer(sellToken, buyToken)
    : oasis.getWorseOffer(pointer);

    ids = new uint[](PAGE_SIZE);
    sellAmounts = new uint[](PAGE_SIZE);
    buyAmounts = new uint[](PAGE_SIZE);
    isActives = new bool[](PAGE_SIZE);

    for (uint i = 0; i < PAGE_SIZE; i++) {
      ids[i] = lastOfferId;
      (sellAmounts[i], , buyAmounts[i],,,) = oasis.offers(lastOfferId);
      isActives[i] = oasis.isActive(lastOfferId);
      lastOfferId = oasis.getWorseOffer(lastOfferId);
    }
  }

  function getOrderBookRowById(uint id) public view returns (uint sellAmount, uint buyAmount) {
    (sellAmount,, buyAmount,,,) = oasis.offers(id);
  }
}
/*
 * Inherits Loopring's IBrokerDelegate and sources liquidity from Oasis
 * 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 WETH/DAI to convert to DAI/WETH
 * 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 brokerRequestAllowance(BrokerData.BrokerApprovalRequest memory request) public returns (bool) {
    require(msg.sender == loopringProtocol, "Oasis MakerBroker: Unauthorized caller");
    require(request.totalRequestedFeeAmount == 0, "Oasis MakerBroker: Cannot be charged a fee");

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

      (uint offerId) = abi.decode(order.extraData, (uint));
      (bool success, bytes memory returnData) = address(oasis).call(
        abi.encodePacked(
          oasis.buy.selector,
          abi.encode(offerId, order.requestedAmountS)
        )
      );
      require(success, "Oasis MakerBroker: Oasis matching failed");
    }

    return false;
    // Does not use onOrderFillReport
  }
// --------------------------------