contract GoodAaveStakingV2 is SimpleStakingV2 {
	using UniswapV2SwapHelper for IHasRouter;

	// Address of the TOKEN/USD oracle from chainlink
	address public tokenUsdOracle;

	//LendingPool of aave
	ILendingPool public lendingPool;

	//Address of the AaveIncentivesController
	IAaveIncentivesController public incentiveController;

	//address of the AAVE/USD oracle
	address public aaveUSDOracle;
	// Gas cost to collect interest from this staking contract
	uint32 public collectInterestGasCost;
	// Gas cost to claim stkAave rewards
	uint32 public stkAaveClaimGasCost;

	address[] public tokenToDaiSwapPath;

	/**
	 * @param _token Token to swap DEFI token
	 * @param _lendingPool LendingPool address
	 * @param _ns Address of the NameService
	 * @param _tokenName Name of the staking token which will be provided to staker for their staking share
	 * @param _tokenSymbol Symbol of the staking token which will be provided to staker for their staking share
	 * @param _tokenSymbol Determines blocks to pass for 1x Multiplier
	 * @param _tokenUsdOracle address of the TOKEN/USD oracle
	 * @param _incentiveController Aave incentive controller which provides AAVE rewards
	 * @param _aaveUSDOracle address of the AAVE/USD oracle
	 */
	function init(
		address _token,
		address _lendingPool,
		INameService _ns,
		string memory _tokenName,
		string memory _tokenSymbol,
		uint64 _maxRewardThreshold,
		address _tokenUsdOracle,
		IAaveIncentivesController _incentiveController,
		address _aaveUSDOracle,
		address[] memory _tokenToDaiSwapPath
	) public {
		lendingPool = ILendingPool(_lendingPool);
		DataTypes.ReserveData memory reserve = lendingPool.getReserveData(_token);
		initialize(
			_token,
			reserve.aTokenAddress,
			_ns,
			_tokenName,
			_tokenSymbol,
			_maxRewardThreshold
		);
		require(
			_tokenToDaiSwapPath[0] == _token &&
				_tokenToDaiSwapPath[_tokenToDaiSwapPath.length - 1] ==
				nameService.getAddress("DAI"),
			"invalid _tokenToDaiSwapPath"
		);
		tokenToDaiSwapPath = _tokenToDaiSwapPath;

		//above  initialize going  to revert on second call, so this is safe
		tokenUsdOracle = _tokenUsdOracle;
		incentiveController = _incentiveController;
		aaveUSDOracle = _aaveUSDOracle;
		collectInterestGasCost = 250000;
		stkAaveClaimGasCost = 50000;
		_approveTokens();
	}

	/**
	 * @dev stake some Token
	 * @param _amount of Token to stake
	 */
	function mintInterestToken(uint256 _amount) internal override {
		lendingPool.deposit(address(token), _amount, address(this), 0);
	}

	/**
	 * @dev redeem Token from aave
	 * @param _amount of token to redeem in Token
	 */
	function redeem(uint256 _amount) internal override {
		uint256 withdrawnAmount = lendingPool.withdraw(
			address(token),
			_amount,
			address(this)
		);
		require(withdrawnAmount > 0, "Withdrawn amount should be bigger than zero");
	}

	/**
	 * @dev Function to redeem aToken for DAI, so reserve knows how to handle it. (reserve can handle dai or cdai)
	 * also transfers stkaave to reserve
	 * @dev _amount of token in iToken
	 * @dev _recipient recipient of the DAI
	 * @return actualTokenGains amount of token redeemed for dai,
			actualRewardTokenGains amount of reward token earned,
			daiAmount total dai received
	 */
	function redeemUnderlyingToDAI(uint256 _amount, address _recipient)
		internal
		override
		returns (
			uint256 actualTokenGains,
			uint256 actualRewardTokenGains,
			uint256 daiAmount
		)
	{
		//out of requested interests to withdraw how much is it safe to swap
		actualTokenGains = IHasRouter(this).maxSafeTokenAmount(
			address(token),
			tokenToDaiSwapPath[1],
			_amount,
			maxLiquidityPercentageSwap
		);

		lendingPool.withdraw(address(token), actualTokenGains, address(this));
		actualTokenGains = token.balanceOf(address(this));

		address[] memory tokenAddress = new address[](1);
		tokenAddress[0] = address(iToken);

		actualRewardTokenGains = incentiveController.claimRewards(
			tokenAddress,
			type(uint256).max,
			avatar
		);

		if (actualTokenGains > 0) {
			daiAmount = IHasRouter(this).swap(
				tokenToDaiSwapPath,
				actualTokenGains,
				0,
				_recipient
			);
		}
	}

	/**
	 * @dev returns decimals of token.
	 */
	function tokenDecimal() internal view override returns (uint256) {
		ERC20 token = ERC20(address(token));
		return uint256(token.decimals());
	}

	/**
	 * @dev returns decimals of interest token.
	 */
	function iTokenDecimal() internal view override returns (uint256) {
		ERC20 aToken = ERC20(address(iToken));
		return uint256(aToken.decimals());
	}

	/**
	 * @dev Function that calculates current interest gains of this staking contract
	 * @param _returnTokenBalanceInUSD determine return token balance of staking contract in USD
	 * @param _returnTokenGainsInUSD determine return token gains of staking contract in USD
	 * @return iTokenGains gains in iToken, tokenGains gains in token,  tokenBalance current balance , balanceInUSD, tokenGainsInUSD
	 */
	function currentGains(
		bool _returnTokenBalanceInUSD,
		bool _returnTokenGainsInUSD
	)
		public
		view
		override
		returns (
			uint256 iTokenGains,
			uint256 tokenGains,
			uint256 tokenBalance,
			uint256 balanceInUSD,
			uint256 tokenGainsInUSD
		)
	{
		ERC20 aToken = ERC20(address(iToken));
		tokenBalance = aToken.balanceOf(address(this));
		balanceInUSD = _returnTokenBalanceInUSD
			? getTokenValueInUSD(tokenUsdOracle, tokenBalance, token.decimals())
			: 0;
		address[] memory tokenAddress = new address[](1);
		tokenAddress[0] = address(token);
		if (tokenBalance <= totalProductivity) {
			return (0, 0, tokenBalance, balanceInUSD, 0);
		}
		iTokenGains = tokenGains = tokenBalance - totalProductivity;

		tokenGainsInUSD = _returnTokenGainsInUSD
			? getTokenValueInUSD(tokenUsdOracle, tokenGains, token.decimals())
			: 0;
	}

	/**
	 * @dev Function to get interest transfer cost for this particular staking contract
	 */
	function getGasCostForInterestTransfer()
		external
		view
		override
		returns (uint32)
	{
		address[] memory tokenAddress = new address[](1);
		tokenAddress[0] = address(iToken);
		uint256 stkAaaveBalance = incentiveController.getRewardsBalance(
			tokenAddress,
			address(this)
		);
		if (stkAaaveBalance > 0)
			return collectInterestGasCost + stkAaveClaimGasCost;

		return collectInterestGasCost;
	}

	/**
	 * @dev Set Gas cost to interest collection for this contract
	 * @param _collectInterestGasCost Gas cost to collect interest
	 * @param _rewardTokenCollectCost gas cost to collect reward tokens
	 */
	function setcollectInterestGasCostParams(
		uint32 _collectInterestGasCost,
		uint32 _rewardTokenCollectCost
	) external {
		_onlyAvatar();
		collectInterestGasCost = _collectInterestGasCost;
		stkAaveClaimGasCost = _rewardTokenCollectCost;
	}

	/**
	 * @dev Calculates worth of given amount of iToken in Token
	 * @param _amount Amount of token to calculate worth in Token
	 * @return Worth of given amount of token in Token
	 */
	function iTokenWorthInToken(uint256 _amount)
		internal
		view
		override
		returns (uint256)
	{
		return _amount; // since aToken is peg to Token 1:1 return exact amount
	}

	function _approveTokens() internal {
		address uniswapRouter = nameService.getAddress("UNISWAP_ROUTER");
		token.approve(uniswapRouter, type(uint256).max);
		token.approve(address(lendingPool), type(uint256).max); // approve the transfers to defi protocol as much as possible in order to save gas
	}
}
/**
 * @title Staking contract that donates earned interest to the DAO
 * allowing stakers to deposit Token
 * or withdraw their stake in Token
 * the contracts buy cToken and can transfer the daily interest to the  DAO
 */
function getGasCostForInterestTransfer()
	external
	view
	override
	returns (uint32)
{
	address[] memory tokenAddress = new address[](1);
	tokenAddress[0] = address(iToken);
	uint256 stkAaaveBalance = incentiveController.getRewardsBalance(
		tokenAddress,
		address(this)
	);
	if (stkAaaveBalance > 0)
		return collectInterestGasCost + stkAaveClaimGasCost;

	return collectInterestGasCost;
}

/**
 	 * @dev Function to get interest transfer cost for this particular staking contract
 	 */