// File: contracts/interfaces/IRewardsTracker.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

interface IRewardsTracker {
  function claimReward() external;

  function depositRewards() external payable;

  function getShares(address wallet) external view returns (uint256);

  function setShare(
    address shareholder,
    uint256 balanceUpdate,
    bool isRemoving
  ) external;
}

// File: contracts/LendingRewards.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

import './RewardsTracker.sol';

contract LendingRewards is RewardsTracker {
  constructor(address _token) RewardsTracker(_token) {}
}

// File: contracts/libraries/BokkyPooBahsDateTimeLibrary.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

// ----------------------------------------------------------------------------
// BokkyPooBah's DateTime Library v1.00
//
// A gas-efficient Solidity date and time library
//
// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary
//
// Tested date range 1970/01/01 to 2345/12/31
//
// Conventions:
// Unit      | Range         | Notes
// :-------- |:-------------:|:-----
// timestamp | >= 0          | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC
// year      | 1970 ... 2345 |
// month     | 1 ... 12      |
// day       | 1 ... 31      |
// hour      | 0 ... 23      |
// minute    | 0 ... 59      |
// second    | 0 ... 59      |
// dayOfWeek | 1 ... 7       | 1 = Monday, ..., 7 = Sunday
//
//
// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018.
//
// GNU Lesser General Public License 3.0
// https://www.gnu.org/licenses/lgpl-3.0.en.html
// ----------------------------------------------------------------------------

library BokkyPooBahsDateTimeLibrary {
  uint constant SECONDS_PER_DAY = 24 * 60 * 60;
  uint constant SECONDS_PER_HOUR = 60 * 60;
  uint constant SECONDS_PER_MINUTE = 60;
  int constant OFFSET19700101 = 2440588;

  uint constant DOW_MON = 1;
  uint constant DOW_TUE = 2;
  uint constant DOW_WED = 3;
  uint constant DOW_THU = 4;
  uint constant DOW_FRI = 5;
  uint constant DOW_SAT = 6;
  uint constant DOW_SUN = 7;

  // ------------------------------------------------------------------------
  // Calculate the number of days from 1970/01/01 to year/month/day using
  // the date conversion algorithm from
  //   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
  // and subtracting the offset 2440588 so that 1970/01/01 is day 0
  //
  // days = day
  //      - 32075
  //      + 1461 * (year + 4800 + (month - 14) / 12) / 4
  //      + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
  //      - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
  //      - offset
  // ------------------------------------------------------------------------
  function _daysFromDate(
    uint year,
    uint month,
    uint day
  ) internal pure returns (uint _days) {
    require(year >= 1970);
    int _year = int(year);
    int _month = int(month);
    int _day = int(day);

    int __days = _day -
      32075 +
      (1461 * (_year + 4800 + (_month - 14) / 12)) /
      4 +
      (367 * (_month - 2 - ((_month - 14) / 12) * 12)) /
      12 -
      (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) /
      4 -
      OFFSET19700101;

    _days = uint(__days);
  }

  // ------------------------------------------------------------------------
  // Calculate year/month/day from the number of days since 1970/01/01 using
  // the date conversion algorithm from
  //   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
  // and adding the offset 2440588 so that 1970/01/01 is day 0
  //
  // int L = days + 68569 + offset
  // int N = 4 * L / 146097
  // L = L - (146097 * N + 3) / 4
  // year = 4000 * (L + 1) / 1461001
  // L = L - 1461 * year / 4 + 31
  // month = 80 * L / 2447
  // dd = L - 2447 * month / 80
  // L = month / 11
  // month = month + 2 - 12 * L
  // year = 100 * (N - 49) + year + L
  // ------------------------------------------------------------------------
  function _daysToDate(
    uint _days
  ) internal pure returns (uint year, uint month, uint day) {
    int __days = int(_days);

    int L = __days + 68569 + OFFSET19700101;
    int N = (4 * L) / 146097;
    L = L - (146097 * N + 3) / 4;
    int _year = (4000 * (L + 1)) / 1461001;
    L = L - (1461 * _year) / 4 + 31;
    int _month = (80 * L) / 2447;
    int _day = L - (2447 * _month) / 80;
    L = _month / 11;
    _month = _month + 2 - 12 * L;
    _year = 100 * (N - 49) + _year + L;

    year = uint(_year);
    month = uint(_month);
    day = uint(_day);
  }

  function timestampFromDate(
    uint year,
    uint month,
    uint day
  ) internal pure returns (uint timestamp) {
    timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY;
  }

  function timestampFromDateTime(
    uint year,
    uint month,
    uint day,
    uint hour,
    uint minute,
    uint second
  ) internal pure returns (uint timestamp) {
    timestamp =
      _daysFromDate(year, month, day) *
      SECONDS_PER_DAY +
      hour *
      SECONDS_PER_HOUR +
      minute *
      SECONDS_PER_MINUTE +
      second;
  }

  function timestampToDate(
    uint timestamp
  ) internal pure returns (uint year, uint month, uint day) {
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
  }

  function timestampToDateTime(
    uint timestamp
  )
    internal
    pure
    returns (
      uint year,
      uint month,
      uint day,
      uint hour,
      uint minute,
      uint second
    )
  {
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    uint secs = timestamp % SECONDS_PER_DAY;
    hour = secs / SECONDS_PER_HOUR;
    secs = secs % SECONDS_PER_HOUR;
    minute = secs / SECONDS_PER_MINUTE;
    second = secs % SECONDS_PER_MINUTE;
  }

  function isValidDate(
    uint year,
    uint month,
    uint day
  ) internal pure returns (bool valid) {
    if (year >= 1970 && month > 0 && month <= 12) {
      uint daysInMonth = _getDaysInMonth(year, month);
      if (day > 0 && day <= daysInMonth) {
        valid = true;
      }
    }
  }

  function isValidDateTime(
    uint year,
    uint month,
    uint day,
    uint hour,
    uint minute,
    uint second
  ) internal pure returns (bool valid) {
    if (isValidDate(year, month, day)) {
      if (hour < 24 && minute < 60 && second < 60) {
        valid = true;
      }
    }
  }

  function isLeapYear(uint timestamp) internal pure returns (bool leapYear) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    leapYear = _isLeapYear(year);
  }

  function _isLeapYear(uint year) internal pure returns (bool leapYear) {
    leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
  }

  function isWeekDay(uint timestamp) internal pure returns (bool weekDay) {
    weekDay = getDayOfWeek(timestamp) <= DOW_FRI;
  }

  function isWeekEnd(uint timestamp) internal pure returns (bool weekEnd) {
    weekEnd = getDayOfWeek(timestamp) >= DOW_SAT;
  }

  function getDaysInMonth(
    uint timestamp
  ) internal pure returns (uint daysInMonth) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    daysInMonth = _getDaysInMonth(year, month);
  }

  function _getDaysInMonth(
    uint year,
    uint month
  ) internal pure returns (uint daysInMonth) {
    if (
      month == 1 ||
      month == 3 ||
      month == 5 ||
      month == 7 ||
      month == 8 ||
      month == 10 ||
      month == 12
    ) {
      daysInMonth = 31;
    } else if (month != 2) {
      daysInMonth = 30;
    } else {
      daysInMonth = _isLeapYear(year) ? 29 : 28;
    }
  }

  // 1 = Monday, 7 = Sunday
  function getDayOfWeek(uint timestamp) internal pure returns (uint dayOfWeek) {
    uint _days = timestamp / SECONDS_PER_DAY;
    dayOfWeek = ((_days + 3) % 7) + 1;
  }

  function getYear(uint timestamp) internal pure returns (uint year) {
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
  }

  function getMonth(uint timestamp) internal pure returns (uint month) {
    uint year;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
  }

  function getDay(uint timestamp) internal pure returns (uint day) {
    uint year;
    uint month;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
  }

  function getHour(uint timestamp) internal pure returns (uint hour) {
    uint secs = timestamp % SECONDS_PER_DAY;
    hour = secs / SECONDS_PER_HOUR;
  }

  function getMinute(uint timestamp) internal pure returns (uint minute) {
    uint secs = timestamp % SECONDS_PER_HOUR;
    minute = secs / SECONDS_PER_MINUTE;
  }

  function getSecond(uint timestamp) internal pure returns (uint second) {
    second = timestamp % SECONDS_PER_MINUTE;
  }

  function addYears(
    uint timestamp,
    uint _years
  ) internal pure returns (uint newTimestamp) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    year += _years;
    uint daysInMonth = _getDaysInMonth(year, month);
    if (day > daysInMonth) {
      day = daysInMonth;
    }
    newTimestamp =
      _daysFromDate(year, month, day) *
      SECONDS_PER_DAY +
      (timestamp % SECONDS_PER_DAY);
    require(newTimestamp >= timestamp);
  }

  function addMonths(
    uint timestamp,
    uint _months
  ) internal pure returns (uint newTimestamp) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    month += _months;
    year += (month - 1) / 12;
    month = ((month - 1) % 12) + 1;
    uint daysInMonth = _getDaysInMonth(year, month);
    if (day > daysInMonth) {
      day = daysInMonth;
    }
    newTimestamp =
      _daysFromDate(year, month, day) *
      SECONDS_PER_DAY +
      (timestamp % SECONDS_PER_DAY);
    require(newTimestamp >= timestamp);
  }

  function addDays(
    uint timestamp,
    uint _days
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp + _days * SECONDS_PER_DAY;
    require(newTimestamp >= timestamp);
  }

  function addHours(
    uint timestamp,
    uint _hours
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp + _hours * SECONDS_PER_HOUR;
    require(newTimestamp >= timestamp);
  }

  function addMinutes(
    uint timestamp,
    uint _minutes
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE;
    require(newTimestamp >= timestamp);
  }

  function addSeconds(
    uint timestamp,
    uint _seconds
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp + _seconds;
    require(newTimestamp >= timestamp);
  }

  function subYears(
    uint timestamp,
    uint _years
  ) internal pure returns (uint newTimestamp) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    year -= _years;
    uint daysInMonth = _getDaysInMonth(year, month);
    if (day > daysInMonth) {
      day = daysInMonth;
    }
    newTimestamp =
      _daysFromDate(year, month, day) *
      SECONDS_PER_DAY +
      (timestamp % SECONDS_PER_DAY);
    require(newTimestamp <= timestamp);
  }

  function subMonths(
    uint timestamp,
    uint _months
  ) internal pure returns (uint newTimestamp) {
    uint year;
    uint month;
    uint day;
    (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    uint yearMonth = year * 12 + (month - 1) - _months;
    year = yearMonth / 12;
    month = (yearMonth % 12) + 1;
    uint daysInMonth = _getDaysInMonth(year, month);
    if (day > daysInMonth) {
      day = daysInMonth;
    }
    newTimestamp =
      _daysFromDate(year, month, day) *
      SECONDS_PER_DAY +
      (timestamp % SECONDS_PER_DAY);
    require(newTimestamp <= timestamp);
  }

  function subDays(
    uint timestamp,
    uint _days
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp - _days * SECONDS_PER_DAY;
    require(newTimestamp <= timestamp);
  }

  function subHours(
    uint timestamp,
    uint _hours
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp - _hours * SECONDS_PER_HOUR;
    require(newTimestamp <= timestamp);
  }

  function subMinutes(
    uint timestamp,
    uint _minutes
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE;
    require(newTimestamp <= timestamp);
  }

  function subSeconds(
    uint timestamp,
    uint _seconds
  ) internal pure returns (uint newTimestamp) {
    newTimestamp = timestamp - _seconds;
    require(newTimestamp <= timestamp);
  }

  function diffYears(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _years) {
    require(fromTimestamp <= toTimestamp);
    uint fromYear;
    uint fromMonth;
    uint fromDay;
    uint toYear;
    uint toMonth;
    uint toDay;
    (fromYear, fromMonth, fromDay) = _daysToDate(
      fromTimestamp / SECONDS_PER_DAY
    );
    (toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
    _years = toYear - fromYear;
  }

  function diffMonths(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _months) {
    require(fromTimestamp <= toTimestamp);
    uint fromYear;
    uint fromMonth;
    uint fromDay;
    uint toYear;
    uint toMonth;
    uint toDay;
    (fromYear, fromMonth, fromDay) = _daysToDate(
      fromTimestamp / SECONDS_PER_DAY
    );
    (toYear, toMonth, toDay) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
    _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth;
  }

  function diffDays(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _days) {
    require(fromTimestamp <= toTimestamp);
    _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY;
  }

  function diffHours(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _hours) {
    require(fromTimestamp <= toTimestamp);
    _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR;
  }

  function diffMinutes(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _minutes) {
    require(fromTimestamp <= toTimestamp);
    _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE;
  }

  function diffSeconds(
    uint fromTimestamp,
    uint toTimestamp
  ) internal pure returns (uint _seconds) {
    require(fromTimestamp <= toTimestamp);
    _seconds = toTimestamp - fromTimestamp;
  }
}

// File: contracts/RewardsTracker.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

import './interfaces/IRewardsTracker.sol';
import './libraries/BokkyPooBahsDateTimeLibrary.sol';

contract RewardsTracker is IRewardsTracker {
  uint256 constant MULTIPLIER = 10 ** 36;
  address public token;
  uint256 public totalStakedUsers;
  uint256 public totalSharesDeposited;

  struct Share {
    uint256 amount;
    uint256 stakedTime;
  }
  struct Reward {
    uint256 totalExcluded;
    uint256 totalRealized;
  }
  mapping(address => Share) private shares;
  mapping(address => Reward) public rewards;

  uint256 public rewardsPerShare;
  uint256 public totalDistributed;
  uint256 public totalRewards;
  mapping(uint256 => uint256) public monthlyRewards;

  event AddShares(address indexed user, uint256 amount);
  event RemoveShares(address indexed user, uint256 amount);
  event ClaimReward(address user);
  event DistributeReward(address indexed user, uint256 amount);
  event DepositRewards(address indexed user, uint256 amountTokens);

  modifier onlyToken() {
    require(msg.sender == token, 'ONLYTOKEN');
    _;
  }

  constructor(address _token) {
    token = _token;
  }

  function setShare(
    address shareholder,
    uint256 balanceUpdate,
    bool isRemoving
  ) public override onlyToken {
    _setShare(shareholder, balanceUpdate, isRemoving);
  }

  function _setShare(
    address shareholder,
    uint256 balanceUpdate,
    bool isRemoving
  ) internal {
    if (isRemoving) {
      _removeShares(shareholder, balanceUpdate);
      emit RemoveShares(shareholder, balanceUpdate);
    } else {
      _addShares(shareholder, balanceUpdate);
      emit AddShares(shareholder, balanceUpdate);
    }
  }

  function _addShares(address shareholder, uint256 amount) private {
    if (shares[shareholder].amount > 0) {
      _distributeReward(shareholder);
    }

    uint256 sharesBefore = shares[shareholder].amount;

    totalSharesDeposited += amount;
    shares[shareholder].amount += amount;
    shares[shareholder].stakedTime = block.timestamp;
    if (sharesBefore == 0 && shares[shareholder].amount > 0) {
      totalStakedUsers++;
    }
    rewards[shareholder].totalExcluded = _cumulativeRewards(
      shares[shareholder].amount
    );
  }

  function _removeShares(address shareholder, uint256 amount) private {
    require(
      shares[shareholder].amount > 0 && amount <= shares[shareholder].amount,
      'REMOVE: no shares'
    );
    _distributeReward(shareholder);

    totalSharesDeposited -= amount;
    shares[shareholder].amount -= amount;
    if (shares[shareholder].amount == 0) {
      totalStakedUsers--;
    }
    rewards[shareholder].totalExcluded = _cumulativeRewards(
      shares[shareholder].amount
    );
  }

  function depositRewards() external payable override {
    _depositRewards(msg.value);
  }

  function _depositRewards(uint256 _amount) internal {
    require(_amount > 0, 'DEPOSIT: no ETH');
    require(totalSharesDeposited > 0, 'DEPOSIT: no shares');

    totalRewards += _amount;
    uint256 _month = beginningOfMonth(block.timestamp);
    monthlyRewards[_month] += _amount;
    rewardsPerShare += (MULTIPLIER * _amount) / totalSharesDeposited;
    emit DepositRewards(msg.sender, _amount);
  }

  function _distributeReward(address shareholder) internal {
    if (shares[shareholder].amount == 0) {
      return;
    }

    uint256 amount = getUnpaid(shareholder);
    rewards[shareholder].totalRealized += amount;
    rewards[shareholder].totalExcluded = _cumulativeRewards(
      shares[shareholder].amount
    );

    if (amount > 0) {
      totalDistributed += amount;
      uint256 _balBefore = address(this).balance;
      (bool success, ) = payable(shareholder).call{ value: amount }('');
      require(success, 'DIST: could not distribute');
      require(address(this).balance >= _balBefore - amount, 'DIST: too much');
      emit DistributeReward(shareholder, amount);
    }
  }

  function claimReward() external override {
    _distributeReward(msg.sender);
    emit ClaimReward(msg.sender);
  }

  function getUnpaid(address shareholder) public view returns (uint256) {
    if (shares[shareholder].amount == 0) {
      return 0;
    }
    uint256 earnedRewards = _cumulativeRewards(shares[shareholder].amount);
    uint256 rewardsExcluded = rewards[shareholder].totalExcluded;
    if (earnedRewards <= rewardsExcluded) {
      return 0;
    }
    return earnedRewards - rewardsExcluded;
  }

  function beginningOfMonth(uint256 _timestamp) public view returns (uint256) {
    (, , uint256 _dayOfMonth) = BokkyPooBahsDateTimeLibrary.timestampToDate(
      _timestamp
    );
    return
      _timestamp - ((_dayOfMonth - 1) * 24 * 60 * 60) - (_timestamp % 1 days);
  }

  function _cumulativeRewards(uint256 share) internal view returns (uint256) {
    return (share * rewardsPerShare) / MULTIPLIER;
  }

  function getShares(address user) external view override returns (uint256) {
    return shares[user].amount;
  }
}