// Contract: DharmaTradeBotV1
// Target function: function cancelLimitOrder(
pragma solidity 0.6.12; // optimization runs: 200, evm version: istanbul
pragma experimental ABIEncoderV2;


interface DharmaTradeBotV1Interface {
  event LimitOrderProcessed(
    address indexed account,
    address indexed suppliedAsset, // Ether = address(0)
    address indexed receivedAsset, // Ether = address(0)
    uint256 suppliedAmount,
    uint256 receivedAmount,
    bytes32 orderID
  );

  event LimitOrderCancelled(bytes32 orderID);
  event RoleModified(Role indexed role, address account);
  event RolePaused(Role indexed role);
  event RoleUnpaused(Role indexed role);

  enum Role {
    BOT_COMMANDER,
    CANCELLER,
    PAUSER
  }

  struct RoleStatus {
    address account;
    bool paused;
  }

  struct LimitOrderArguments {
    address account;
    address assetToSupply;        // Ether = address(0)
    address assetToReceive;       // Ether = address(0)
    uint256 maximumAmountToSupply;
    uint256 maximumPriceToAccept; // represented as a mantissa (n * 10^18)
    uint256 expiration;
    bytes32 salt;
  }

  struct LimitOrderExecutionArguments {
    uint256 amountToSupply; // will be lower than maximum for partial fills
    bytes signatures;
    address tradeTarget;
    bytes tradeData;
  }

  function processLimitOrder(
    LimitOrderArguments calldata args,
    LimitOrderExecutionArguments calldata executionArgs
  ) external returns (uint256 amountReceived);

  function cancelLimitOrder(LimitOrderArguments calldata args) external;

  function setRole(Role role, address account) external;

  function removeRole(Role role) external;

  function pause(Role role) external;

  function unpause(Role role) external;

  function isPaused(Role role) external view returns (bool paused);

  function isRole(Role role) external view returns (bool hasRole);
  
  function getOrderID(
    LimitOrderArguments calldata args
  ) external view returns (bytes32 orderID, bool valid);

  function getBotCommander() external view returns (address botCommander);
  
  function getCanceller() external view returns (address canceller);

  function getPauser() external view returns (address pauser);
}


interface SupportingContractInterface {
  function setApproval(address token, uint256 amount) external;
}


interface ERC1271Interface {
  function isValidSignature(
    bytes calldata data, bytes calldata signatures
  ) external view returns (bytes4 magicValue);
}


interface ERC20Interface {
  function transfer(address, uint256) external returns (bool);
  function transferFrom(address, address, uint256) external returns (bool);
  function approve(address, uint256) external returns (bool);
  function balanceOf(address) external view returns (uint256);
  function allowance(address, address) external view returns (uint256);
}



library SafeMath {
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a, "SafeMath: addition overflow");

    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a, "SafeMath: subtraction overflow");

    return a - b;
  }

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    require(c / a == b, "SafeMath: multiplication overflow");

    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b > 0, "SafeMath: division by zero");
    return a / b;
  }
}


/**
 * @notice Contract module which provides a basic access control mechanism,
 * where there is an account (an owner) that can be granted exclusive access
 * to specific functions.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be aplied to your functions to restrict their use to
 * the owner.
 *
 * In order to transfer ownership, a recipient must be specified, at which point
 * the specified recipient can call `acceptOwnership` and take ownership.
 */
contract TwoStepOwnable {
  event OwnershipTransferred(
    address indexed previousOwner,
    address indexed newOwner
  );

  address private _owner;

  address private _newPotentialOwner;

  /**
   * @notice Initialize contract with transaction submitter as initial owner.
   */
  constructor() internal {
    _owner = tx.origin;
    emit OwnershipTransferred(address(0), _owner);
  }

  /**
   * @notice Allows a new account (`newOwner`) to accept ownership.
   * Can only be called by the current owner.
   */
  function transferOwnership(address newOwner) external onlyOwner {
    require(
      newOwner != address(0),
      "TwoStepOwnable: new potential owner is the zero address."
    );

    _newPotentialOwner = newOwner;
  }

  /**
   * @notice Cancel a transfer of ownership to a new account.
   * Can only be called by the current owner.
   */
  function cancelOwnershipTransfer() external onlyOwner {
    delete _newPotentialOwner;
  }

  /**
   * @notice Transfers ownership of the contract to the caller.
   * Can only be called by a new potential owner set by the current owner.
   */
  function acceptOwnership() external {
    require(
      msg.sender == _newPotentialOwner,
      "TwoStepOwnable: current owner must set caller as new potential owner."
    );

    delete _newPotentialOwner;

    emit OwnershipTransferred(_owner, msg.sender);

    _owner = msg.sender;
  }

  /**
   * @notice Returns the address of the current owner.
   */
  function owner() external view returns (address) {
    return _owner;
  }

  /**
   * @notice Returns true if the caller is the current owner.
   */
  function isOwner() public view returns (bool) {
    return msg.sender == _owner;
  }

  /**
   * @notice Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(isOwner(), "TwoStepOwnable: caller is not the owner.");
    _;
  }
}


/**
 * @title DharmaTradeBotV1
 * @author 0age
 * @notice DharmaTradeBot is a contract for performing meta-transaction-enabled
 * limit orders against external automated money markets or other sources of
 * on-chain liquidity. Eth-to-Token trades require that `triggerEtherTransfer`
 * is implemented on the account making the trade, and all trades require
 * that the account implements `isValidSignature` as specified by ERC-1271,
 * as well as a `setApproval` function, in order to enable meta-transactions.
 * For Eth-to-token trades, the Eth amount
 */
contract DharmaTradeBotV1 is DharmaTradeBotV1Interface, TwoStepOwnable {
  using SafeMath for uint256;

  // Maintain a role status mapping with assigned accounts and paused states.
  mapping(uint256 => RoleStatus) private _roles;

  // Maintain a mapping of invalid meta-transaction order IDs.
  mapping (bytes32 => bool) private _invalidMetaTxHashes;

  ERC20Interface private constant _ETHERIZER = ERC20Interface(
    0x723B51b72Ae89A3d0c2a2760f0458307a1Baa191
  );

  receive() external payable {}

  /**
   * @notice Only callable by the bot commander or the owner. Enforces the
   * expiration (or skips if it is set to zero) and trade size, validates
   * the execution signatures using ERC-1271 against the account, sets
   * approval to transfer the supplied token on behalf of that account,
   * pulls in the necessary supplied tokens, sets an allowance for the
   * provided trade target, calls the trade target with supplied data,
   * ensures that the call was successful, calculates the required amount
   * that must be received back based on the supplied amount and price,
   * ensures that at least that amount was returned, sends it to the
   * account, and emits an event. Use the null address to signify that
   * the supplied or retained asset is Ether.
   * @return amountReceived The amount received back from the trade.
   */
  function processLimitOrder(
    LimitOrderArguments calldata args,
    LimitOrderExecutionArguments calldata executionArgs
  ) external override onlyOwnerOr(Role.BOT_COMMANDER) returns (
    uint256 amountReceived
  ) {
    _enforceExpiration(args.expiration);

    require(
      executionArgs.amountToSupply <= args.maximumAmountToSupply,
      "Cannot supply more than the maximum authorized amount."
    );
    
    require(
      args.assetToSupply != args.assetToReceive,
      "Asset to supply and asset to receive cannot be identical."
    );

    if (executionArgs.tradeData.length >= 4) {
      require(
        abi.decode(
          abi.encodePacked(executionArgs.tradeData[:4], bytes28(0)), (bytes4)
        ) != SupportingContractInterface.setApproval.selector,
        "Trade data has a prohibited function selector."
      );
    }

    // Construct order's "context" and use it to validate meta-transaction.
    bytes memory context = _constructLimitOrderContext(args);
    bytes32 orderID = _validateMetaTransaction(
      args.account, context, executionArgs.signatures
    );

    // Determine asset supplied (use Etherizer for Ether) and ether value.
    ERC20Interface assetToSupply;
    uint256 etherValue;
    
    if (args.assetToSupply == address(0)) {
      assetToSupply = _ETHERIZER;
      etherValue = executionArgs.amountToSupply;
    } else {
      assetToSupply = ERC20Interface(args.assetToSupply);
      etherValue = 0;

      // Ensure that target has allowance to transfer tokens.
      _grantApprovalIfNecessary(
        assetToSupply, executionArgs.tradeTarget, executionArgs.amountToSupply
      );
    }

    // Call `setApproval` on the supplying account.
    SupportingContractInterface(args.account).setApproval(
      address(assetToSupply), executionArgs.amountToSupply
    );

    // Make the transfer in.
    _transferInToken(
      assetToSupply, args.account, executionArgs.amountToSupply
    );

    // Call into target, supplying data and value, and revert on failure.
    _performCallToTradeTarget(
      executionArgs.tradeTarget, executionArgs.tradeData, etherValue
    );

    // Determine amount expected back using supplied amount and price.
    uint256 amountExpected = (
      executionArgs.amountToSupply.mul(1e18)
    ).div(
      args.maximumPriceToAccept
    );
 
    if (args.assetToReceive == address(0)) {
      // Determine ether balance held by this contract.
      amountReceived = address(this).balance;  
      
      // Ensure that enough Ether was received.
      require(
        amountReceived >= amountExpected,
        "Trade did not result in the expected amount of Ether."
      );
      
      // Transfer the Ether out and revert on failure.
      _transferEther(args.account, amountReceived);
    } else {
      ERC20Interface assetToReceive = ERC20Interface(args.assetToReceive);

      // Determine balance of received tokens held by this contract.
      amountReceived = assetToReceive.balanceOf(address(this));
      
      // Ensure that enough tokens were received.
      require(
        amountReceived >= amountExpected,
        "Trade did not result in the expected amount of tokens."
      );

      // Transfer the tokens and revert on failure.
      _transferOutToken(assetToReceive, args.account, amountReceived);
    }

    emit LimitOrderProcessed(
      args.account,
      args.assetToSupply,
      args.assetToReceive,
      executionArgs.amountToSupply,
      amountReceived,
      orderID
    );
  }