// Contract: OPTICS
// Target function: function dividendOf(address _owner, address _rewardToken) external view override returns(uint256)
 Returns the integer division of two unsigned integers. Reverts with custom message on
      * division by zero. The result is rounded towards zero.
      *
      * Counterpart to Solidity's `/` operator. Note: this function uses a
      * `revert` opcode (which leaves remaining gas untouched) while Solidity
      * uses an invalid opcode to revert (consuming all remaining gas).
      *
      * Requirements:
      *
      * - The divisor cannot be zero.
      */
     function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
          require(b > 0, errorMessage);
          uint256 c = a / b;
          //assert(a == b * c + a % b); //There is no case in which this doesn't hold

          return c;
     }

     /**
      * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
      * Reverts when dividing by zero.
      *
      * Counterpart to Solidity's `%` operator. This function uses a `revert`
      * opcode (which leaves remaining gas untouched) while Solidity uses an
      * invalid opcode to revert (consuming all remaining gas).
      *
      * Requirements:
      *
      * - The divisor cannot be zero.
      */
     function mod(uint256 a, uint256 b) internal pure returns (uint256) {
          return mod(a, b, "SafeMath: modulo by zero");
     }

     /**
      * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
      * Reverts with custom message when dividing by zero.
      *
      * Counterpart to Solidity's `%` operator. This function uses a `revert`
      * opcode (which leaves remaining gas untouched) while Solidity uses an
      * invalid opcode to revert (consuming all remaining gas).
      *
      * Requirements:
      *
      * - The divisor cannot be zero.
      */
     function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
          require(b != 0, errorMessage);
          return a % b;
     }
}

contract Ownable is Context {
     address private _owner;

     event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
     
     /**
      * @dev Initializes the contract setting the deployer as the initial owner.
      */
     constructor () {
          address msgSender = _msgSender();
          _owner = msgSender;
          emit OwnershipTransferred(address(0), msgSender);
     }

     /**
      * @dev Returns the address of the current owner.
      */
     function owner() public view returns (address) {
          return _owner;
     }

     /**
      * @dev Throws if called by any account other than the owner.
      */
     modifier onlyOwner() {
          require(_owner == _msgSender(), "Ownable: caller is not the owner");
          _;
     }

     /**
      * @dev Leaves the contract without owner. It will not be possible to call
      * `onlyOwner` functions anymore. Can only be called by the current owner.
      *
      * NOTE: Renouncing ownership will leave the contract without an owner,
      * thereby removing any functionality that is only available to the owner.
      */
     function renounceOwnership() public virtual onlyOwner {
          emit OwnershipTransferred(_owner, address(0));
          _owner = address(0);
     }

     /**
      * @dev Transfers ownership of the contract to a new account (`newOwner`).
      * Can only be called by the current owner.
      */
     function transferOwnership(address newOwner) public virtual onlyOwner {
          require(newOwner != address(0), "Ownable: new owner is the zero address");
          emit OwnershipTransferred(_owner, newOwner);
          _owner = newOwner;
     }
}



library SafeMathInt {
     int256 private constant MIN_INT256 = int256(1) << 255;
     int256 private constant MAX_INT256 = ~(int256(1) << 255);

     /**
      * @dev Multiplies two int256 variables and fails on overflow.
      */
     function mul(int256 a, int256 b) internal pure returns (int256) {
          int256 c = a * b;

          //Detect overflow when multiplying MIN_INT256 with -1
          require(c != MIN_INT256 || (a & MIN_INT256) != (b & MIN_INT256));
          require((b == 0) || (c / b == a));
          return c;
     }

     /**
      * @dev Division of two int256 variables and fails on overflow.
      */
     function div(int256 a, int256 b) internal pure returns (int256) {
          //Prevent overflow when dividing MIN_INT256 by -1
          require(b != -1 || a != MIN_INT256);

          //Solidity already throws when dividing by 0.
          return a / b;
     }

     /**
      * @dev Subtracts two int256 variables and fails on overflow.
      */
     function sub(int256 a, int256 b) internal pure returns (int256) {
          int256 c = a - b;
          require((b >= 0 && c <= a) || (b < 0 && c > a));
          return c;
     }

     /**
      * @dev Adds two int256 variables and fails on overflow.
      */
     function add(int256 a, int256 b) internal pure returns (int256) {
          int256 c = a + b;
          require((b >= 0 && c >= a) || (b < 0 && c < a));
          return c;
     }

     /**
      * @dev Converts to absolute value, and fails on overflow.
      */
     function abs(int256 a) internal pure returns (int256) {
          require(a != MIN_INT256);
          return a < 0 ? -a : a;
     }


     function toUint256Safe(int256 a) internal pure returns (uint256) {
          require(a >= 0);
          return uint256(a);
     }
}

library SafeMathUint {
  function toInt256Safe(uint256 a) internal pure returns (int256) {
     int256 b = int256(a);
     require(b >= 0);
     return b;
  }
}


interface IUniswapV2Router01 {
     function factory() external pure returns (address);
     function WETH() external pure returns (address);

     function addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
     ) external returns (uint amountA, uint amountB, uint liquidity);
     function addLiquidityETH(
          address token,
          uint amountTokenDesired,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
     ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
     function removeLiquidity(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
     ) external returns (uint amountA, uint amountB);
     function removeLiquidityETH(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
     ) external returns (uint amountToken, uint amountETH);
     function removeLiquidityWithPermit(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
     ) external returns (uint amountA, uint amountB);
     function removeLiquidityETHWithPermit(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
     ) external returns (uint amountToken, uint amountETH);
     function swapExactTokensForTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
     ) external returns (uint[] memory amounts);
     function swapTokensForExactTokens(
          uint amountOut,
          uint amountInMax,
          address[] calldata path,
          address to,
          uint deadline
     ) external returns (uint[] memory amounts);
     function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
          external
          payable
          returns (uint[] memory amounts);
     function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
          external
          returns (uint[] memory amounts);
     function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
          external
          returns (uint[] memory amounts);
     function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
          external
          payable
          returns (uint[] memory amounts);

     function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
     function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
     function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
     function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
     function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
     function removeLiquidityETHSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
     ) external returns (uint amountETH);
     function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
     ) external returns (uint amountETH);

     function swapExactTokensForTokensSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
     ) external;
     function swapExactETHForTokensSupportingFeeOnTransferTokens(
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
     ) external payable;
     function swapExactTokensForETHSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
     ) external;
}

contract DividendPayingToken is DividendPayingTokenInterface, DividendPayingTokenOptionalInterface, Ownable {
  using SafeMath for uint256;
  using SafeMathUint for uint256;
  using SafeMathInt for int256;

  //With `magnitude`, we can properly distribute dividends even if the amount of received ether is small.
  //For more discussion about choosing the value of `magnitude`,
  // see https://github.com/ethereum/EIPs/issues/1726#issuecomment-472352728
  uint256 constant internal magnitude = 2**128;

  mapping(address => uint256) internal magnifiedDividendPerShare;
  address[] public rewardTokens;
  address public nextRewardToken;
  uint256 public rewardTokenCounter;
  
  IUniswapV2Router02 public immutable uniswapV2Router;
  
  
  //About dividendCorrection:
  //If the token balance of a `_user` is never changed, the dividend of `_user` can be computed with:
  //  `dividendOf(_user) = dividendPerShare * balanceOf(_user)`.
  //When `balanceOf(_user)` is changed (via minting/burning/transferring tokens),
  //  `dividendOf(_user)` should not be changed,
  //  but the computed value of `dividendPerShare * balanceOf(_user)` is changed.
  //To keep the `dividendOf(_user)` unchanged, we add a correction term:
  //  `dividendOf(_user) = dividendPerShare * balanceOf(_user) + dividendCorrectionOf(_user)`,
  //  where `dividendCorrectionOf(_user)` is updated whenever `balanceOf(_user)` is changed:
  //  `dividendCorrectionOf(_user) = dividendPerShare * (old balanceOf(_user)) - (new balanceOf(_user))`.
  //So now `dividendOf(_user)` returns the same value before and after `balanceOf(_user)` is changed.
  mapping(address => mapping(address => int256)) internal magnifiedDividendCorrections;
  mapping(address => mapping(address => uint256)) internal withdrawnDividends;
  
  mapping (address => uint256) public holderBalance;
  uint256 public totalBalance;

  mapping(address => uint256) public totalDividendsDistributed;
  
  constructor(){
       IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); //router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
       uniswapV2Router = _uniswapV2Router; 
       
       //Mainnet

       rewardTokens.push(address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)); //USDC - 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
       
       nextRewardToken = rewardTokens[0];
  }

  

  ///@dev Distributes dividends whenever ether is paid to this contract.
  receive() external payable {
     distributeDividends();
  }

  ///@notice Distributes ether to token holders as dividends.
  ///@dev It reverts if the total supply of tokens is 0.
  ///It emits the `DividendsDistributed` event if the amount of received ether is greater than 0.
  ///About undistributed ether:
  ///  In each distribution, there is a small amount of ether not distributed,
  ///     the magnified amount of which is
  ///     `(msg.value * magnitude) % totalSupply()`.
  ///  With a well-chosen `magnitude`, the amount of undistributed ether
  ///     (de-magnified) in a distribution can be less than 1 wei.
  ///  We can actually keep track of the undistributed ether in a distribution
  ///     and try to distribute it in the next distribution,
  ///     but keeping track of such data on-chain costs much more than
  ///     the saved ether, so we don't do that.
     
  function distributeDividends() public override payable { 
     require(totalBalance > 0);
     uint256 initialBalance = IERC20(nextRewardToken).balanceOf(address(this));
     buyTokens(msg.value, nextRewardToken);
     uint256 newBalance = IERC20(nextRewardToken).balanceOf(address(this)).sub(initialBalance);
     if (newBalance > 0) {
       magnifiedDividendPerShare[nextRewardToken] = magnifiedDividendPerShare[nextRewardToken].add(
          (newBalance).mul(magnitude) / totalBalance
       );
       emit DividendsDistributed(msg.sender, newBalance);

       totalDividendsDistributed[nextRewardToken] = totalDividendsDistributed[nextRewardToken].add(newBalance);
     }
     rewardTokenCounter = rewardTokenCounter == rewardTokens.length - 1 ? 0 : rewardTokenCounter + 1;
     nextRewardToken = rewardTokens[rewardTokenCounter];
  }
  
  //useful for buybacks or to reclaim any BNB on the contract in a way that helps holders.
     function buyTokens(uint256 bnbAmountInWei, address rewardToken) internal {
          //generate the uniswap pair path of weth -> eth
          address[] memory path = new address[](2);
          path[0] = uniswapV2Router.WETH();
          path[1] = rewardToken;

          //make the swap
          uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: bnbAmountInWei}(
               0, //accept any amount of Ethereum
               path,
               address(this),
               block.timestamp
          );
     }
  
  ///@notice Withdraws the ether distributed to the sender.
  ///@dev It emits a `DividendWithdrawn` event if the amount of withdrawn ether is greater than 0.
  function withdrawDividend(address _rewardToken) external virtual override {
     _withdrawDividendOfUser(payable(msg.sender), _rewardToken);
  }

  ///@notice Withdraws the ether distributed to the sender.
  ///@dev It emits a `DividendWithdrawn` event if the amount of withdrawn ether is greater than 0.
  function _withdrawDividendOfUser(address payable user, address _rewardToken) internal returns (uint256) {
     uint256 _withdrawableDividend = withdrawableDividendOf(user, _rewardToken);
     if (_withdrawableDividend > 0) {
       withdrawnDividends[user][_rewardToken] = withdrawnDividends[user][_rewardToken].add(_withdrawableDividend);
       emit DividendWithdrawn(user, _withdrawableDividend);
       IERC20(_rewardToken).transfer(user, _withdrawableDividend);
       return _withdrawableDividend;
     }

     return 0;
  }