// Contract: DSLOProtocol
// Target function: function fillBatchOrdersTo(FillBatchOrdersParams calldata params)
Collected(address indexed recipient, address indexed token, uint256 amount);

  // address feeRecipient;
  //   uint32 amountTokenFeePercent;
  //   bool isTakerAssetFee;

  // Fixed-size order part with core information
  // `feeConfig`
  //      feeRecipient = address(uint160(params.order.feeConfig))
  //      amountTokenFeePercent = uint32(params.order.feeConfig >> 160)
  //      isTakerAssetFee = (params.order.feeConfig >> 192) != 0
  struct StaticOrder {
    uint256 salt;
    address makerAsset;
    address takerAsset;
    address maker;
    address receiver;
    address allowedSender; // equals to Zero address on public orders
    uint256 makingAmount;
    uint256 takingAmount;
    uint256 feeConfig; // bit slot  1 -> 32 -> 160: isTakerAssetFee - amountTokenFeePercent - feeRecipient
  }

  // `StaticOrder` extension including variable-sized additional order meta information
  // `feeConfig`
  //      feeRecipient = address(uint160(params.order.feeConfig))
  //      amountTokenFeePercent = uint32(params.order.feeConfig >> 160)
  //      isTakerAssetFee = (params.order.feeConfig >> 192) != 0
  struct Order {
    uint256 salt;
    address makerAsset;
    address takerAsset;
    address maker;
    address receiver;
    address allowedSender; // equals to Zero address on public orders
    uint256 makingAmount;
    uint256 takingAmount;
    uint256 feeConfig; // bit slot  1 -> 32 -> 160: isTakerAssetFee - amountTokenFeePercent - feeRecipient
    bytes makerAssetData;
    bytes takerAssetData;
    bytes getMakerAmount; // this.staticcall(abi.encodePacked(bytes, swapTakerAmount)) => (swapMakerAmount)
    bytes getTakerAmount; // this.staticcall(abi.encodePacked(bytes, swapMakerAmount)) => (swapTakerAmount)
    bytes predicate; // this.staticcall(bytes) => (bool)
    bytes interaction;
  }

  struct DSOrder {
    bytes32 orderHash;
    uint32 opExpireTime;
  }

  struct Signature {
    bytes orderSignature; // Signature to confirm quote ownership
    bytes opSignature; // OP Signature to confirm quote ownership
  }

  struct AmountData {
    uint256 makingAmount;
    uint256 takingAmount;
    uint256 thresholdAmount;
  }

  struct FillOrderParams {
    Order order;
    Signature signature;
    uint32 opExpireTime;
    address target;
    bytes callbackData;
  }

  struct FillBatchOrdersParams {
    Order[] orders;
    Signature[] signatures;
    uint32[] opExpireTimes;
    uint256 takingAmount;
    uint256 thresholdAmount;
    address target;
  }
}

/*
“Copyright (c) 2019-2021 1inch 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions: 
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software. 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE”.
*/

/// @title Library with gas efficient alternatives to `abi.decode`
library ArgumentsDecoder {
  function decodeUint256(bytes memory data) internal pure returns (uint256) {
    uint256 value;
    assembly {
      // solhint-disable-line no-inline-assembly
      value := mload(add(data, 0x20))
    }
    return value;
  }

  function decodeBool(bytes memory data) internal pure returns (bool) {
    bool value;
    assembly {
      // solhint-disable-line no-inline-assembly
      value := eq(mload(add(data, 0x20)), 1)
    }
    return value;
  }

  function decodeTargetAndCalldata(bytes memory data) internal pure returns (address, bytes memory) {
    address target;
    bytes memory args;
    assembly {
      // solhint-disable-line no-inline-assembly
      target := mload(add(data, 0x14))
      args := add(data, 0x14)
      mstore(args, sub(mload(data), 0x14))
    }
    return (target, args);
  }

  function decodeTargetAndData(bytes calldata data) internal pure returns (address, bytes calldata) {
    address target;
    bytes calldata args;
    assembly {
      // solhint-disable-line no-inline-assembly
      target := shr(96, calldataload(data.offset))
    }
    args = data[20:];
    return (target, args);
  }
}

/// @title Regular Limit Order mixin
abstract contract DSOrderMixin is
  EIP712,
  AmountCalculator,
  NonceManager,
  PredicateHelper,
  KyberSwapRole,
  IDSOrderMixin
{
  using Address for address;
  using ArgumentsDecoder for bytes;

  bytes32 public constant LIMIT_ORDER_TYPEHASH = keccak256(
    'Order(uint256 salt,address makerAsset,address takerAsset,address maker,address receiver,address allowedSender,uint256 makingAmount,uint256 takingAmount,uint256 feeConfig,bytes makerAssetData,bytes takerAssetData,bytes getMakerAmount,bytes getTakerAmount,bytes predicate,bytes interaction)'
  );

  bytes32 public constant DS_LIMIT_ORDER_TYPEHASH =
    keccak256('DSOrder(bytes32 orderHash,uint32 opExpireTime)');

  uint256 private constant _ORDER_DOES_NOT_EXIST = 0;
  uint256 private constant _ORDER_FILLED = 1;
  uint24 internal constant FEE_UNITS = 100_000;
  address private _dsOrderSigner;

  /// @notice Stores unfilled amounts for each order plus one
  /// Therefore 0 means order doesn't exist and 1 means order was filled
  mapping(bytes32 => uint256) private _remaining;
  mapping(address => bool) interactionWhitelist;

  /// @notice Update interaction target whitelist
  function updateInteractionWhitelist(address _address, bool isWhitelist) external onlyOwner {
    interactionWhitelist[_address] = isWhitelist;
    emit UpdatedInteractionWhitelist(_address, isWhitelist);
  }

  /// @notice Update dsOrderSigner
  function updateDSOrderSigner(address _address) external onlyOwner {
    _dsOrderSigner = _address;
    emit UpdatedDSOrderSigner(_address);
  }

  /// @notice Hard cancels order by setting remaining amount to zero
  function cancelOrder(Order calldata order) public {
    require(order.maker == msg.sender, 'LOP: Access denied');

    bytes32 orderHash = hashOrder(order);
    uint256 orderRemaining = _remaining[orderHash];
    require(orderRemaining != _ORDER_FILLED, 'LOP: already filled');
    emit OrderCanceled(msg.sender, orderHash, orderRemaining);
    _remaining[orderHash] = _ORDER_FILLED;
  }

  /// @notice Cancels multiple orders by setting remaining amount to zero
  function cancelBatchOrders(Order[] calldata orders) external {
    for (uint256 i; i < orders.length;) {
      cancelOrder(orders[i]);
      unchecked {
        ++i;
      }
    }
  }

  /// @notice Same as `fillOrder`
  /// @param params FillOrderParams:
  ///   - Order order: quote to fill
  ///   - Signature signature: Signature to confirm quote ownership
  ///   - uint32 expireTime: OP Signature expire time
  ///   - address target: Maker asset recipient
  ///   - bytes callbackData: CallbackData to callback to the msg.sender after receiving the makingAmount, the msg.sender transfer takingAmount to the maker after this call
  /// @param amountData amount data struct
  /// @param sender if msg.sender is address(this), set msg.sender = sender (msg.sender of the previous call)
  /// @return actualMakingAmount
  /// @return actualTakingAmount
  /// @return feeCollected
  function fillOrderTo(
    FillOrderParams calldata params,
    AmountData memory amountData,
    address sender
  )
    public
    whenNotPaused
    returns (
      uint256, /* actualMakingAmount */
      uint256, /* actualTakingAmount */
      uint256 /* feeCollected */
    )
  {
    require(params.target != address(0), 'LOP: zero target is forbidden');

    address _msgSender = msg.sender;
    if (sender != address(0) && msg.sender == address(this)) {
      _msgSender = sender;
    }
    bytes32 orderHash = hashOrder(params.order);

    // handle double signature
    {
      bytes32 dsOrderHash =
        hashDSOrder(DSOrder({orderHash: orderHash, opExpireTime: params.opExpireTime}));
      require(params.opExpireTime >= block.timestamp, 'LOP: expired');
      require(
        SignatureChecker.isValidSignatureNow(
          _dsOrderSigner, dsOrderHash, params.signature.opSignature
        ),
        'LOP: bad op signature'
      );
    }

    {
      // Stack too deep
      uint256 remainingMakerAmount = _remaining[orderHash];

      if (remainingMakerAmount == _ORDER_FILLED) {
        revert('LOP: invalid order');
      }
      require(
        params.order.allowedSender == address(0) || params.order.allowedSender == _msgSender,
        'LOP: private order'
      );
      if (remainingMakerAmount == _ORDER_DOES_NOT_EXIST) {
        // First fill: validate order
        require(
          SignatureChecker.isValidSignatureNow(
            params.order.maker, orderHash, params.signature.orderSignature
          ),
          'LOP: bad signature'
        );
        remainingMakerAmount = params.order.makingAmount;
      } else {
        unchecked {
          remainingMakerAmount -= 1;
        }
      }

      // Check if order is valid
      if (params.order.predicate.length > 0) {
        bool isValidPredicate = checkPredicate(params.order);

        require(isValidPredicate, 'LOP: predicate returned false');
      }

      // Compute maker and taker assets amount
      if ((amountData.takingAmount == 0) == (amountData.makingAmount == 0)) {
        revert('LOP: only one amount should be 0');
      } else if (amountData.takingAmount == 0) {
        uint256 requestedMakingAmount = amountData.makingAmount;
        if (amountData.makingAmount > remainingMakerAmount) {
          amountData.makingAmount = remainingMakerAmount;
        }

        amountData.takingAmount = _callGetter(
          params.order.getTakerAmount,
          params.order.makingAmount,
          amountData.makingAmount,
          params.order.takingAmount
        );
        // check that actual rate is not worse than what was expected
        // takingAmount / makingAmount <= thresholdAmount / requestedMakingAmount
        require(
          amountData.takingAmount * requestedMakingAmount
            <= amountData.thresholdAmount * amountData.makingAmount,
          'LOP: taking amount too high'
        );
      } else {
        uint256 requestedTakingAmount = amountData.takingAmount;
        amountData.makingAmount = _callGetter(
          params.order.getMakerAmount,
          params.order.takingAmount,
          amountData.takingAmount,
          params.order.makingAmount
        );

        if (amountData.makingAmount > remainingMakerAmount) {
          amountData.makingAmount = remainingMakerAmount;
          amountData.takingAmount = _callGetter(
            params.order.getTakerAmount,
            params.order.makingAmount,
            amountData.makingAmount,
            params.order.takingAmount
          );
        }
        // check that actual rate is not worse than what was expected
        // makingAmount / takingAmount >= thresholdAmount / requestedTakingAmount
        require(
          amountData.makingAmount * requestedTakingAmount
            >= amountData.thresholdAmount * amountData.takingAmount,
          'LOP: making amount too low'
        );
      }

      require(
        amountData.makingAmount > 0 && amountData.takingAmount > 0, "LOP: can't swap 0 amount"
      );

      // Update remaining amount in storage
      unchecked {
        remainingMakerAmount = remainingMakerAmount - amountData.makingAmount;
        _remaining[orderHash] = remainingMakerAmount + 1;
      }
      emit OrderFilled(
        _msgSender,
        orderHash,
        remainingMakerAmount,
        amountData.makingAmount,
        amountData.takingAmount
      );
    }

    // feeRecipient = address(uint160(params.order.feeConfig))
    // amountTokenFeePercent = uint32(params.order.feeConfig >> 160)
    // isTakerAssetFee = (params.order.feeConfig >> 192) != 0
    uint256 feeAmount;
    // feeRecipient != 0 && amountTokenFeePercent != 0 : have fee
    if (
      address(uint160(params.order.feeConfig)) != address(0)
        && uint32(params.order.feeConfig >> 160) != 0
    ) {
      if ((params.order.feeConfig >> 192) != 0) {
        feeAmount = (
          amountData.takingAmount * uint32(params.order.feeConfig >> 160) + FEE_UNITS - 1
        ) / FEE_UNITS;

        // Maker => Taker
        _makeCall(
          params.order.makerAsset,
          abi.encodePacked(
            IERC20.transferFrom.selector,
            uint256(uint160(params.order.maker)),
            uint256(uint160(params.target)),
            amountData.makingAmount,
            params.order.makerAssetData
          )
        );

        // Callback to _msgSender
        if (params.callbackData.length != 0) {
          ILimitOrderCallee(_msgSender).limitOrderCall(
            amountData.makingAmount, amountData.takingAmount + feeAmount, params.callbackData
          );
        }

        // Taker => FeeRecipient
        _makeCall(
          params.order.takerAsset,
          abi.encodePacked(
            IERC20.transferFrom.selector,
            uint256(uint160(_msgSender)),
            uint256(uint160(params.order.feeConfig)),
            feeAmount,
            params.order.takerAssetData
          )
        );
        emit FeeCollected(
          address(uint160(params.order.feeConfig)), params.order.takerAsset, feeAmount
        );
      } else {
        feeAmount = (
          amountData.makingAmount * uint32(params.order.feeConfig >> 160) + FEE_UNITS - 1
        ) / FEE_UNITS;

        // Maker => FeeRecipient
        _makeCall(
          params.order.makerAsset,
          abi.encodePacked(
            IERC20.transferFrom.selector,
            uint256(uint160(params.order.maker)),
            uint256(uint160(params.order.feeConfig)),
            feeAmount,
            params.order.makerAssetData
          )
        );
        emit FeeCollected(
          address(uint160(params.order.feeConfig)), params.order.makerAsset, feeAmount
        );

        // Maker => Taker
        _makeCall(
          params.order.makerAsset,
          abi.encodePacked(
            IERC20.transferFrom.selector,
            uint256(uint160(params.order.maker)),
            uint256(uint160(params.target)),
            amountData.makingAmount - feeAmount,
            params.order.makerAssetData
          )
        );

        // Callback to _msgSender
        if (params.callbackData.length != 0) {
          ILimitOrderCallee(_msgSender).limitOrderCall(
            amountData.makingAmount, amountData.takingAmount, params.callbackData
          );
        }
      }
    } else {
      // no fee
      // Maker => Taker
      _makeCall(
        params.order.makerAsset,
        abi.encodePacked(
          IERC20.transferFrom.selector,
          uint256(uint160(params.order.maker)),
          uint256(uint160(params.target)),
          amountData.makingAmount,
          params.order.makerAssetData
        )
      );

      // Callback to _msgSender
      if (params.callbackData.length != 0) {
        ILimitOrderCallee(_msgSender).limitOrderCall(
          amountData.makingAmount, amountData.takingAmount, params.callbackData
        );
      }
    }

    // Taker => Maker
    _makeCall(
      params.order.takerAsset,
      abi.encodePacked(
        IERC20.transferFrom.selector,
        uint256(uint160(_msgSender)),
        uint256(
          uint160(params.order.receiver == address(0) ? params.order.maker : params.order.receiver)
        ),
        amountData.takingAmount,
        params.order.takerAssetData
      )
    );

    // Maker can handle funds interactively
    if (params.order.interaction.length >= 20) {
      // proceed only if interaction length is enough to store address
      (address interactionTarget, bytes memory interactionData) =
        params.order.interaction.decodeTargetAndCalldata();
      require(
        interactionWhitelist[interactionTarget], 'LOP: the interaction target is not whitelisted'
      );
      InteractiveNotificationReceiver(interactionTarget).notifyFillOrder(
        _msgSender,
        params.order.makerAsset,
        params.order.takerAsset,
        amountData.makingAmount,
        amountData.takingAmount,
        interactionData
      );
    }

    return (params.order.feeConfig >> 192) != 0
      ? (amountData.makingAmount, amountData.takingAmount + feeAmount, feeAmount)
      : (amountData.makingAmount, amountData.takingAmount, feeAmount);
  }