contract LiquidityERC20Swap is Destructible, Pausable, WithdrawOperator, WithFee, RateNormalization, IErc20Swap {
  using SafeMath for uint;
  using SafeERC20 for IERC20;
  using SafeStaticCallERC20 for IBadStaticCallERC20;
  uint constant expScale = 1e18;

  address public token1;
  address public token2;
  address public feeToken;

  uint public rate1to2;
  uint public rate2to1;
  uint public rateDeadline;

  event SetRate(uint rate1to2, uint rate2to1, uint deadline);

  constructor (address _token1, address _token2, address payable _wallet, uint _spread) public
    WithFee(_wallet, _spread)
  {
    require(_token1 != address(0) && _token2 != address(0), "invalid tokens");
    token1 = _token1;
    token2 = _token2;
    feeToken = _token2;
  }

  function setFeeToken(address _token) public onlyOwner {
    require(_token == token1 || _token == token2, "invalid token address");
    feeToken = _token;
  }


  function setRate(uint _rate1to2, uint _rate2to1, uint _deadline) public onlyOwner {
    require(_deadline > now, "deadline <= now");
    _setRate(_rate1to2, _rate2to1, _deadline);
  }

  function _setRate(uint _rate1to2, uint _rate2to1, uint _deadline) internal {
    rate1to2 = _rate1to2;
    rate2to1 = _rate2to1;
    rateDeadline = _deadline;
    emit SetRate(rate1to2, rate2to1, rateDeadline);
  }

  function _getDestAmount(address src, address dest, uint srcAmount) internal view returns(uint toUserAmount, uint feeAmount) {
    uint rate;
    if (now > rateDeadline) {
      rate = 0;
    } else if (src == token1 && dest == token2) {
      rate = rate1to2;
    } else if (src == token2 && dest == token1) {
      rate = rate2to1;
    } else {
      rate = 0;
    }
    if (feeToken == src) {
      feeAmount = _getFee(srcAmount);
      toUserAmount = srcAmount.sub(feeAmount).mul(rate).div(expScale);
    } else if (feeToken == dest) {
      uint intermediate = srcAmount.mul(rate).div(expScale);
      feeAmount = _getFee(intermediate);
      toUserAmount = intermediate.sub(feeAmount);
    } else {
      toUserAmount = 0;
      feeAmount = 0;
    }
  }

  // @dev Calculate the conversion rate considering the fee and the balance
  function getRate(address src, address dest, uint256 srcAmount) public view
    returns(uint rate, uint slippage)
  {
    (uint toUserAmount, ) = _getDestAmount(src, dest, srcAmount);
    // if no enough balance rate will be 0
    if (toUserAmount > IERC20(dest).balanceOf(address(this))) {
      rate = 0;
    } else {
      rate = normalizeRate(src, dest, toUserAmount.mul(expScale).div(srcAmount));
    }
    slippage = rate;
  }

  function swap(address src, uint srcAmount, address dest, uint /* maxDestAmount */, uint minConversionRate)
    public payable whenNotPaused
  {
    require(msg.value == 0, "ethers not supported");
    require(srcAmount != 0, "srcAmount == 0");
    require(
      IBadStaticCallERC20(src).allowance(msg.sender, address(this)) >= srcAmount, // 0xdd62ed3e
      "ERC20 allowance < srcAmount"
    );

    (uint toUserAmount, uint feeAmount) = _getDestAmount(src, dest, srcAmount);

    uint actualRate = normalizeRate(src, dest, toUserAmount.mul(expScale).div(srcAmount));
    require(toUserAmount > 0, "toUserAmount == 0");
    require(actualRate >= minConversionRate, "rate < minConversionRate");

    require(toUserAmount <= IBadStaticCallERC20(dest).balanceOf(address(this)), "Insufficient funds");

    // get user's tokens.
    uint srcExpectedBalance = IBadStaticCallERC20(src).balanceOf(address(this)).add(srcAmount);
    IERC20(src).safeTransferFrom(msg.sender, address(this), srcAmount);   // 0x23b872dd
    require(srcExpectedBalance == IBadStaticCallERC20(src).balanceOf(address(this)), "Unexpected src balance");

    _payFee(feeToken, feeAmount);

    // send tokens to the user
    IERC20(dest).safeTransfer(msg.sender, toUserAmount);

    emit LogTokenSwap(
      msg.sender,
      src,
      srcAmount,
      dest,
      toUserAmount
    );
  }

}
function getRate(address src, address dest, uint256 srcAmount) public view
  returns(uint rate, uint slippage)
{
  (uint toUserAmount, ) = _getDestAmount(src, dest, srcAmount);
  // if no enough balance rate will be 0
  if (toUserAmount > IERC20(dest).balanceOf(address(this))) {
    rate = 0;
  } else {
    rate = normalizeRate(src, dest, toUserAmount.mul(expScale).div(srcAmount));
  }
  slippage = rate;
}

// @dev Calculate the conversion rate considering the fee and the balance