contract CompoundRewardToken is ContinuousRewardToken {
  using SafeMath for uint256;
  using SafeERC20 for IERC20;

  uint256 constant private BASE = 1e18;
  uint256 constant private DAYS_PER_YEAR = 365;
  uint256 constant private BLOCKS_PER_DAY = 5760;// at a rate of 15 seconds per block, https://github.com/compound-finance/compound-protocol/blob/23eac9425accafb82551777c93896ee7678a85a3/contracts/JumpRateModel.sol#L18
  uint256 constant private BLOCKS_PER_YEAR = BLOCKS_PER_DAY * DAYS_PER_YEAR;

  /// @notice The address of cToken contract
  ICToken public cToken;
  /// @notice The address of COMP token
  address public comp;

  /**
   * @notice Construct a new Compound reward token
   * @param name ERC-20 name of this token
   * @param symbol ERC-20 symbol of this token
   * @param _cToken The address of cToken contract
   * @param delegate The address of reward owner
   */
  constructor(
    string memory name,
    string memory symbol,
    ICToken _cToken,
    address delegate
  ) ERC20(name, symbol) ContinuousRewardToken(_cToken.underlying(), delegate) public {
    cToken = _cToken;
    comp = cToken.comptroller().getCompAddress();

    // This contract doesn't support cComp or cEther, use special case contract for them
    require(underlying != comp, "CompoundRewardToken: does not support cComp usecase");

    IERC20(underlying).approve(address(cToken), uint256(-1));
  }

  function _rewardTokens() override internal view returns (address[] memory) {
    address[] memory tokens = new address[](2);
    (tokens[0], tokens[1]) = (underlying, comp);
    return tokens;
  }

  function _balanceOfReward(address rewardToken) override internal view returns (uint256) {
    require(rewardToken == underlying || rewardToken == comp, "CompoundRewardToken: not reward token");
    if (rewardToken == underlying) {
      // get the value of this contract's cTokens in the underlying, and subtract total CRT mint amount to get interest
      uint256 underlyingBalance = balanceOfCTokenUnderlying(address(this));
      uint256 totalSupply = totalSupply();
      // Due to rounding errors, it is possible the total supply is greater than the underlying balance by 1 wei, return 0 in this case
      // This is a transient case which will resolve itself once rewards are earned
      return totalSupply > underlyingBalance ? 0 : underlyingBalance.sub(totalSupply);
    } else {
      return getCompRewards();
    }
  }

  /**
   * @notice Annual Percentage Reward for the specific reward token. Measured in relation to the base units of the underlying asset vs base units of the accrued reward token.
   * @param rewardToken Reward token address
   * @dev Underlying asset rate is an APY, Comp rate is an APR
   * @return APY times 10^18
   */
  function rate(address rewardToken) override external view returns (uint256) {
    require(rewardToken == underlying || rewardToken == comp, "CompoundRewardToken: not reward token");
    if (rewardToken == underlying) {
      return getUnderlyingRate();
    } else {
      return getCompRate();
    }
  }

  function _supply(uint256 amount) override internal {
    require(cToken.mint(amount) == 0, "CompoundRewardToken: minting cToken failed");
  }

  function _redeem(uint256 amount) override internal {
    require(cToken.redeemUnderlying(amount) == 0, "CompoundRewardToken: redeeming cToken failed");
  }

  function _claim(address claimToken, uint256 amount) override internal {
    require(claimToken == underlying || claimToken == comp, "CompoundRewardToken: not reward token");
    if (claimToken == underlying) {
      require(cToken.redeemUnderlying(amount) == 0, "CompoundRewardToken: redeemUnderlying failed");
    } else {
      claimComp();
    }
  }

  /*** Compound Interface ***/

  //@dev Only shows the COMP accrued up until the last interaction with the cToken.
  function getCompRewards() internal view returns (uint256) {
    uint256 compAccrued = cToken.comptroller().compAccrued(address(this));
    return IERC20(comp).balanceOf(address(this)).add(compAccrued);
  }

  function claimComp() internal {
    ICToken[] memory cTokens = new ICToken[](1);
    cTokens[0] = cToken;
    cToken.comptroller().claimComp(address(this), cTokens);
  }

  function getUnderlyingRate() internal view returns (uint256) {
    uint256 supplyRatePerBlock = cToken.supplyRatePerBlock();
    return rateToAPY(supplyRatePerBlock);
  }

  // @dev APY = (1 + rate) ^ 365 - 1
  function rateToAPY(uint apr) internal pure returns (uint256) {
    uint256 ratePerDay = apr.mul(BLOCKS_PER_DAY).add(BASE);
    uint256 acc = ratePerDay;
    for (uint256 i = 1; i < DAYS_PER_YEAR; i++) {
      acc = acc.mul(ratePerDay).div(BASE);
    }
    return acc.sub(BASE);
  }

  function getCompRate() internal view returns (uint256) {
    IComptroller comptroller = cToken.comptroller();
    uint256 compForMarketPerYear = comptroller.compSpeeds(address(cToken)).mul(BLOCKS_PER_YEAR);
    uint256 exchangeRate = cToken.exchangeRateStored();
    uint256 totalSupply = cToken.totalSupply();
    uint256 totalUnderlying = totalSupply.mul(exchangeRate).div(BASE);
    return compForMarketPerYear.mul(BASE).div(totalUnderlying);
  }

  // @dev returns the amount of underlying that this contract's cTokens can be redeemed for
  function balanceOfCTokenUnderlying(address owner) internal view returns (uint256) {
    uint256 exchangeRate = cToken.exchangeRateStored();
    uint256 scaledMantissa = exchangeRate.mul(cToken.balanceOf(owner));
    // Note: We are not using careful math here as we're performing a division that cannot fail
    return scaledMantissa /  BASE;
  }
}
/**
 * @title CompoundRewardToken contract
 * @notice ERC20 token which wraps Compound underlying and COMP rewards
 * @author
 */
function getCompRewards() internal view returns (uint256) {
  uint256 compAccrued = cToken.comptroller().compAccrued(address(this));
  return IERC20(comp).balanceOf(address(this)).add(compAccrued);
}

//@dev Only shows the COMP accrued up until the last interaction with the cToken.