// File: FarmStaking.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.7.0;

library Address {
  function isContract(address account) internal view returns (bool) {
    uint256 size;
    assembly { size := extcodesize(account) }
    return size > 0;
  }

  function sendValue(address payable recipient, uint256 amount) internal {
    require(address(this).balance >= amount, "Address: insufficient balance");
    (bool success, ) = recipient.call{ value: amount }("");
    require(success, "Address: unable to send value, recipient may have reverted");
  }

  function functionCall(address target, bytes memory data) internal returns (bytes memory) {
    return functionCall(target, data, "Address: low-level call failed");
  }

  function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
    return _functionCallWithValue(target, data, 0, errorMessage);
  }

  function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
    return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
  }

  function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
    require(address(this).balance >= value, "Address: insufficient balance for call");
    return _functionCallWithValue(target, data, value, errorMessage);
  }

  function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) {
    require(isContract(target), "Address: call to non-contract");
    (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
    if (success) {
      return returndata;
    } else {
      if (returndata.length > 0) {
        assembly {
          let returndata_size := mload(returndata)
          revert(add(32, returndata), returndata_size)
        }
      } else {
        revert(errorMessage);
      }
    }
  }
}

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) {
    return sub(a, b, "SafeMath: subtraction overflow");
  }

  function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    require(b <= a, errorMessage);
    uint256 c = a - b;
    return c;
  }

  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) {
    return div(a, b, "SafeMath: division by zero");
  }

  function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    require(b > 0, errorMessage);
    uint256 c = a / b;
    return c;
  }

  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    return mod(a, b, "SafeMath: modulo by zero");
  }

  function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    require(b != 0, errorMessage);
    return a % b;
  }
}

library SafeERC20 {
  using SafeMath for uint256;
  using Address for address;

  function safeTransfer(IERC20 token, address to, uint256 value) internal {
    _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
  }

  function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
    _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
  }

  function safeApprove(IERC20 token, address spender, uint256 value) internal {
    require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance");
    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
  }

  function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    uint256 newAllowance = token.allowance(address(this), spender).add(value);
    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
  }

  function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
  }

  function _callOptionalReturn(IERC20 token, bytes memory data) private {
    bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
    if (returndata.length > 0) {
      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }
  }
}

library TransferHelper {
  function safeApprove(address token, address to, uint value) internal {
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: APPROVE_FAILED');
  }

  function safeTransfer(address token, address to, uint value) internal {
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
  }

  function safeTransferFrom(address token, address from, address to, uint value) internal {
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
  }
}

interface IERC20 {
  function totalSupply() external view returns (uint256);
  function balanceOf(address account) external view returns (uint256);
  function transfer(address recipient, uint256 amount) external returns (bool);
  function allowance(address owner, address spender) external view returns (uint256);
  function approve(address spender, uint256 amount) external returns (bool);
  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
  event Transfer(address indexed from, address indexed to, uint256 value);
  event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface IFarmFactory {
  function userEnteredFarm(address _user) external;
  function userLeftFarm(address _user) external;
  function registerFarm(address _farmAddress) external;
}

contract RewardHolder {
  using SafeMath for uint256;
  using SafeERC20 for IERC20;

  address public farmGenerator;
  address public farm;
  address public rewardToken;
  uint256 public farmableSupply;

  constructor(address _farmGenerator, address _farm) public {
    farmGenerator = _farmGenerator;
    farm = _farm;
  }

  function init(address _rewardToken, uint256 _amount) public {
    address msgSender = msg.sender;
    TransferHelper.safeTransferFrom(_rewardToken, msgSender, address(this), _amount);
    TransferHelper.safeApprove(_rewardToken, farm, _amount);
    rewardToken = _rewardToken;
    farmableSupply = _amount;
  }
}


contract FarmStaking {
  using SafeMath for uint256;
  using SafeERC20 for IERC20;

  /// @notice information stuct on each user than stakes tokens.
  struct UserInfo {
    uint256 amount;     // How many tokens the user has provided.
    uint256 rewardDebt; // Reward debt.
  }

  /// @notice all the settings for this farm in one struct
  struct FarmInfo {
    IERC20 token;
    IERC20 rewardToken;
    address rewardHolder;
    uint256 startBlock;
    uint256 blockReward;
    uint256 bonusEndBlock;
    uint256 bonus;
    uint256 endBlock;
    uint256 lastRewardBlock;   // Last block number that reward distribution occurs.
    uint256 accRewardPerShare; // Accumulated Rewards per share, times 1e12
    uint256 farmableSupply;    // set in init, total amount of tokens farmable
    uint256 numFarmers;
  }

  /// @notice farm type id. Useful for back-end systems to know how to read the contract (ABI) as we plan to launch multiple farm types
  uint256 public farmType = 2;

  IFarmFactory public factory;
  address public farmGenerator;

  FarmInfo public farmInfo;

  /// @notice information on each user than stakes tokens
  mapping (address => UserInfo) public userInfo;

  event Deposit(address indexed user, uint256 amount);
  event Withdraw(address indexed user, uint256 amount);
  event EmergencyWithdraw(address indexed user, uint256 amount);

  constructor(address _factory, address _farmGenerator) public {
    factory = IFarmFactory(_factory);
    farmGenerator = _farmGenerator;
  }

  /**
   * @notice initialize the farming contract. This is called only once upon farm creation and the FarmGenerator ensures the farm has the correct paramaters
   */
  function init(address _rewardHolder, IERC20 _rewardToken, uint256 _amount, IERC20 _token, uint256 _blockReward, uint256 _startBlock, uint256 _endBlock, uint256 _bonusEndBlock, uint256 _bonus) public {
    address msgSender = _msgSender();
    require(msgSender == address(farmGenerator), 'FORBIDDEN');

    farmInfo.rewardToken = _rewardToken;
    farmInfo.rewardHolder = _rewardHolder;

    farmInfo.startBlock = _startBlock;
    farmInfo.blockReward = _blockReward;
    farmInfo.bonusEndBlock = _bonusEndBlock;
    farmInfo.bonus = _bonus;

    uint256 lastRewardBlock = block.number > _startBlock ? block.number : _startBlock;
    farmInfo.token = _token;
    farmInfo.lastRewardBlock = lastRewardBlock;
    farmInfo.accRewardPerShare = 0;

    farmInfo.endBlock = _endBlock;
    farmInfo.farmableSupply = _amount;
  }

  /**
   * @notice Gets the reward multiplier over the given _from_block until _to block
   * @param _from_block the start of the period to measure rewards for
   * @param _to the end of the period to measure rewards for
   * @return The weighted multiplier for the given period
   */
  function getMultiplier(uint256 _from_block, uint256 _to) public view returns (uint256) {
    uint256 _from = _from_block >= farmInfo.startBlock ? _from_block : farmInfo.startBlock;
    uint256 to = farmInfo.endBlock > _to ? _to : farmInfo.endBlock;
    if (to <= farmInfo.bonusEndBlock) {
      return to.sub(_from).mul(farmInfo.bonus);
    } else if (_from >= farmInfo.bonusEndBlock) {
      return to.sub(_from);
    } else {
      return farmInfo.bonusEndBlock.sub(_from).mul(farmInfo.bonus).add(
        to.sub(farmInfo.bonusEndBlock)
      );
    }
  }

  /**
   * @notice function to see accumulated balance of reward token for specified user
   * @param _user the user for whom unclaimed tokens will be shown
   * @return total amount of withdrawable reward tokens
   */
  function pendingReward(address _user) external view returns (uint256) {
    UserInfo storage user = userInfo[_user];
    uint256 accRewardPerShare = farmInfo.accRewardPerShare;
    uint256 tokenSupply = farmInfo.token.balanceOf(address(this));
    if (block.number > farmInfo.lastRewardBlock && tokenSupply != 0) {
      uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
      uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
      accRewardPerShare = accRewardPerShare.add(tokenReward.mul(1e12).div(tokenSupply));
    }
    return user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
  }

  /**
   * @notice updates pool information to be up to date to the current block
   */
  function updatePool() public {
    if (block.number <= farmInfo.lastRewardBlock) {
      return;
    }
    uint256 tokenSupply = farmInfo.token.balanceOf(address(this));
    if (tokenSupply == 0) {
      farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
      return;
    }
    uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
    uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
    farmInfo.accRewardPerShare = farmInfo.accRewardPerShare.add(tokenReward.mul(1e12).div(tokenSupply));
    farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
  }

  /**
   * @notice deposit token function for msgSender
   * @param _amount the total deposit amount
   */
  function deposit(uint256 _amount) public {
    address msgSender = _msgSender();
    UserInfo storage user = userInfo[msgSender];
    updatePool();
    if (user.amount > 0) {
      uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
      safeRewardTransfer(msgSender, pending);
    }
    if (user.amount == 0 && _amount > 0) {
      factory.userEnteredFarm(msgSender);
      farmInfo.numFarmers = farmInfo.numFarmers.add(1);
    }
    farmInfo.token.safeTransferFrom(address(msgSender), address(this), _amount);
    user.amount = user.amount.add(_amount);
    user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
    emit Deposit(msgSender, _amount);
  }

  /**
   * @notice withdraw token function for msgSender
   * @param _amount the total withdrawable amount
   */
  function withdraw(uint256 _amount) public {
    address msgSender = _msgSender();
    UserInfo storage user = userInfo[msgSender];
    require(user.amount >= _amount, "INSUFFICIENT");
    updatePool();
    if (user.amount == _amount && _amount > 0) {
      factory.userLeftFarm(msgSender);
      farmInfo.numFarmers = farmInfo.numFarmers.sub(1);
    }
    uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
    safeRewardTransfer(msgSender, pending);
    user.amount = user.amount.sub(_amount);
    user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
    farmInfo.token.safeTransfer(address(msgSender), _amount);
    emit Withdraw(msgSender, _amount);
  }

  /**
   * @notice emergency functoin to withdraw tokens and forego harvest rewards. Important to protect users tokens
   */
  function emergencyWithdraw() public {
    address msgSender = _msgSender();
    UserInfo storage user = userInfo[msgSender];
    farmInfo.token.safeTransfer(address(msgSender), user.amount);
    emit EmergencyWithdraw(msgSender, user.amount);
    if (user.amount > 0) {
      factory.userLeftFarm(msgSender);
      farmInfo.numFarmers = farmInfo.numFarmers.sub(1);
    }
    user.amount = 0;
    user.rewardDebt = 0;
  }

  /**
   * @notice Safe reward transfer function, just in case a rounding error causes pool to not have enough reward tokens
   * @param _to the user address to transfer tokens to
   * @param _amount the total amount of tokens to transfer
   */
  function safeRewardTransfer(address _to, uint256 _amount) internal {
    uint256 rewardBal = farmInfo.rewardToken.balanceOf(farmInfo.rewardHolder);
    if (_amount > rewardBal) {
      farmInfo.rewardToken.transferFrom(farmInfo.rewardHolder, _to, rewardBal);
    } else {
      farmInfo.rewardToken.transferFrom(farmInfo.rewardHolder, _to, _amount);
    }
  }

  function _msgSender() internal view virtual returns (address payable) {
    return msg.sender;
  }
}

// File: FarmStakingGenerator.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.7.0;
import "./FarmStaking.sol";

abstract contract Context {
  function _msgSender() internal view virtual returns (address payable) {
    return msg.sender;
  }
}

abstract contract Ownable is Context {
  address private _owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  constructor () internal {
    address msgSender = _msgSender();
    _owner = msgSender;
    emit OwnershipTransferred(address(0), msgSender);
  }

  function owner() public view returns (address) {
    return _owner;
  }

  modifier onlyOwner() {
    require(_owner == _msgSender(), "Ownable: caller is not the owner");
    _;
  }

  function renounceOwnership() public virtual onlyOwner {
    emit OwnershipTransferred(_owner, address(0));
    _owner = address(0);
  }

  function transferOwnership(address newOwner) public virtual onlyOwner {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
  }
}


contract FarmStakingGenerator is Context, Ownable {
  using SafeMath for uint256;
  IFarmFactory public factory;

  struct FarmParameters {
    uint256 bonusBlocks;
    uint256 totalBonusReward;
    uint256 numBlocks;
    uint256 endBlock;
    uint256 requiredAmount;
  }

  constructor(IFarmFactory _factory) public {
    factory = _factory;
  }

  /**
   * @notice Determine the endBlock based on inputs. Used on the front end to show the exact settings the Farm contract will be deployed with
   */
  function determineEndBlock(uint256 _amount, uint256 _blockReward, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus) public pure returns (uint256, uint256) {
    FarmParameters memory params;
    params.bonusBlocks = _bonusEndBlock.sub(_startBlock);
    params.totalBonusReward = params.bonusBlocks.mul(_bonus).mul(_blockReward);
    params.numBlocks = _amount.sub(params.totalBonusReward).div(_blockReward);
    params.endBlock = params.numBlocks.add(params.bonusBlocks).add(_startBlock);

    uint256 nonBonusBlocks = params.endBlock.sub(_bonusEndBlock);
    uint256 effectiveBlocks = params.bonusBlocks.mul(_bonus).add(nonBonusBlocks);
    uint256 requiredAmount = _blockReward.mul(effectiveBlocks);
    return (params.endBlock, requiredAmount);
  }

  /**
   * @notice Determine the blockReward based on inputs specifying an end date. Used on the front end to show the exact settings the Farm contract will be deployed with
   */
  function determineBlockReward(uint256 _amount, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus, uint256 _endBlock) public pure returns (uint256, uint256) {
    uint256 bonusBlocks = _bonusEndBlock.sub(_startBlock);
    uint256 nonBonusBlocks = _endBlock.sub(_bonusEndBlock);
    uint256 effectiveBlocks = bonusBlocks.mul(_bonus).add(nonBonusBlocks);
    uint256 blockReward = _amount.div(effectiveBlocks);
    uint256 requiredAmount = blockReward.mul(effectiveBlocks);
    return (blockReward, requiredAmount);
  }

  /**
   * @notice Creates a new FarmStaking contract and registers it in the 
   * .sol. All farming rewards are locked in the FarmStaking Contract
   */
  function createFarmStaking(IERC20 _rewardToken, uint256 _amount, IERC20 _token, uint256 _blockReward, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus) public onlyOwner returns (address){
    require(_startBlock > block.number, 'START'); // ideally at least 24 hours more to give farmers time
    require(_bonus > 0, 'BONUS');
    require(address(_rewardToken) != address(0), 'REWARD TOKEN');
    require(address(_token) != address(0), 'TOKEN');
    require(_blockReward > 1000, 'BLOCK REWARD'); // minimum 1000 divisibility per block reward

    FarmParameters memory params;
    (params.endBlock, params.requiredAmount) = determineEndBlock(_amount, _blockReward, _startBlock, _bonusEndBlock, _bonus);

    TransferHelper.safeTransferFrom(address(_rewardToken), address(_msgSender()), address(this), params.requiredAmount);
    FarmStaking newFarm = new FarmStaking(address(factory), address(this));
    RewardHolder newRewardHolder = new RewardHolder(address(this), address(newFarm));
    TransferHelper.safeApprove(address(_rewardToken), address(newRewardHolder), params.requiredAmount);
    newRewardHolder.init(address(_rewardToken), params.requiredAmount);
    newFarm.init(address(newRewardHolder), _rewardToken, params.requiredAmount, _token, _blockReward, _startBlock, params.endBlock, _bonusEndBlock, _bonus);

    factory.registerFarm(address(newFarm));
    return (address(newFarm));
  }
}