/**
 * @dev updates the redirected balance of the user. If the user is not redirecting his
 * interest, nothing is executed.
 * @param _user the address of the user for which the interest is being accumulated
 * @param _balanceToAdd the amount to add to the redirected balance
 * @param _balanceToRemove the amount to remove from the redirected balance
 **/

function updateRedirectedBalanceOfRedirectionAddressInternal(
        address _user,
        uint256 _balanceToAdd,
        uint256 _balanceToRemove
    ) internal {

        address redirectionAddress = interestRedirectionAddresses[_user];
        //if there isn't any redirection, nothing to be done
        if(redirectionAddress == address(0)){
            return;
        }

        //compound balances of the redirected address
        (,,uint256 balanceIncrease, uint256 index) = cumulateBalanceInternal(redirectionAddress);

        //updating the redirected balance
        redirectedBalances[redirectionAddress] = redirectedBalances[redirectionAddress]
            .add(_balanceToAdd)
            .sub(_balanceToRemove);

        //if the interest of redirectionAddress is also being redirected, we need to update
        //the redirected balance of the redirection target by adding the balance increase
        address targetOfRedirectionAddress = interestRedirectionAddresses[redirectionAddress];

        if(targetOfRedirectionAddress != address(0)){
            redirectedBalances[targetOfRedirectionAddress] = redirectedBalances[targetOfRedirectionAddress].add(balanceIncrease);
        }

        emit RedirectedBalanceUpdated(
            redirectionAddress,
            balanceIncrease,
            index,
            _balanceToAdd,
            _balanceToRemove
        );
    }


/**
 * @dev executes the transfer of aTokens, invoked by both _transfer() and
 *      transferOnLiquidation()
 * @param _from the address from which transfer the aTokens
 * @param _to the destination address
 * @param _value the amount to transfer
 **/

function executeTransferInternal(
        address _from,
        address _to,
        uint256 _value
    ) internal {

        require(_value > 0, "Transferred amount needs to be greater than zero");

        //cumulate the balance of the sender
        (,
        uint256 fromBalance,
        uint256 fromBalanceIncrease,
        uint256 fromIndex
        ) = cumulateBalanceInternal(_from);

        //cumulate the balance of the receiver
        (,
        ,
        uint256 toBalanceIncrease,
        uint256 toIndex
        ) = cumulateBalanceInternal(_to);

        //if the sender is redirecting his interest towards someone else,
        //adds to the redirected balance the accrued interest and removes the amount
        //being transferred
        updateRedirectedBalanceOfRedirectionAddressInternal(_from, fromBalanceIncrease, _value);

        //if the receiver is redirecting his interest towards someone else,
        //adds to the redirected balance the accrued interest and the amount
        //being transferred
        updateRedirectedBalanceOfRedirectionAddressInternal(_to, toBalanceIncrease.add(_value), 0);

        //performs the transfer
        super._transfer(_from, _to, _value);

        bool fromIndexReset = false;
        //reset the user data if the remaining balance is 0
        if(fromBalance.sub(_value) == 0){
            fromIndexReset = resetDataOnZeroBalanceInternal(_from);
        }

        emit BalanceTransfer(
            _from,
            _to,
            _value,
            fromBalanceIncrease,
            toBalanceIncrease,
            fromIndexReset ? 0 : fromIndex,
            toIndex
        );
    }


/**
 * @dev function to reset the interest stream redirection and the user index, if the
 * user has no balance left.
 * @param _user the address of the user
 * @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value
 **/

function resetDataOnZeroBalanceInternal(address _user) internal returns(bool) {

        //if the user has 0 principal balance, the interest stream redirection gets reset
        interestRedirectionAddresses[_user] = address(0);

        //emits a InterestStreamRedirected event to notify that the redirection has been reset
        emit InterestStreamRedirected(_user, address(0),0,0,0);

        //if the redirected balance is also 0, we clear up the user index
        if(redirectedBalances[_user] == 0){
            userIndexes[_user] = 0;
            return true;
        }
        else{
            return false;
        }
    }


/**
 * @dev updates the state of the core as a result of a deposit action
 * @param _reserve the address of the reserve in which the deposit is happening
 * @param _user the address of the the user depositing
 * @param _amount the amount being deposited
 * @param _isFirstDeposit true if the user is depositing for the first time
 **/

function updateStateOnDeposit(
        address _reserve,
        address _user,
        uint256 _amount,
        bool _isFirstDeposit
    ) external onlyLendingPool {
        reserves[_reserve].updateCumulativeIndexes();
        updateReserveInterestRatesAndTimestampInternal(_reserve, _amount, 0);

        if (_isFirstDeposit) {
            //if this is the first deposit of the user, we configure the deposit as enabled to be used as collateral
            setUserUseReserveAsCollateral(_reserve, _user, true);
        }
    }


/**
 * @dev updates the state of the core as a result of a redeem action
 * @param _reserve the address of the reserve in which the redeem is happening
 * @param _user the address of the the user redeeming
 * @param _amountRedeemed the amount being redeemed
 * @param _userRedeemedEverything true if the user is redeeming everything
 **/

function updateStateOnRedeem(
        address _reserve,
        address _user,
        uint256 _amountRedeemed,
        bool _userRedeemedEverything
    ) external onlyLendingPool {
        //compound liquidity and variable borrow interests
        reserves[_reserve].updateCumulativeIndexes();
        updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountRedeemed);

        //if user redeemed everything the useReserveAsCollateral flag is reset
        if (_userRedeemedEverything) {
            setUserUseReserveAsCollateral(_reserve, _user, false);
        }
    }


/**
 * @dev updates the state of the core as a consequence of a borrow action.
 * @param _reserve the address of the reserve on which the user is borrowing
 * @param _user the address of the borrower
 * @param _amountBorrowed the new amount borrowed
 * @param _borrowFee the fee on the amount borrowed
 * @param _rateMode the borrow rate mode (stable, variable)
 * @return the new borrow rate for the user
 **/

function updateStateOnBorrow(
        address _reserve,
        address _user,
        uint256 _amountBorrowed,
        uint256 _borrowFee,
        CoreLibrary.InterestRateMode _rateMode
    ) external onlyLendingPool returns (uint256, uint256) {
        // getting the previous borrow data of the user
        (uint256 principalBorrowBalance, , uint256 balanceIncrease) = getUserBorrowBalances(
            _reserve,
            _user
        );

        updateReserveStateOnBorrowInternal(
            _reserve,
            _user,
            principalBorrowBalance,
            balanceIncrease,
            _amountBorrowed,
            _rateMode
        );

        updateUserStateOnBorrowInternal(
            _reserve,
            _user,
            _amountBorrowed,
            balanceIncrease,
            _borrowFee,
            _rateMode
        );

        updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountBorrowed);

        return (getUserCurrentBorrowRate(_reserve, _user), balanceIncrease);
    }


/**
 * @dev updates the state of the core as a consequence of a repay action.
 * @param _reserve the address of the reserve on which the user is repaying
 * @param _user the address of the borrower
 * @param _paybackAmountMinusFees the amount being paid back minus fees
 * @param _originationFeeRepaid the fee on the amount that is being repaid
 * @param _balanceIncrease the accrued interest on the borrowed amount
 * @param _repaidWholeLoan true if the user is repaying the whole loan
 **/

function updateStateOnRepay(
        address _reserve,
        address _user,
        uint256 _paybackAmountMinusFees,
        uint256 _originationFeeRepaid,
        uint256 _balanceIncrease,
        bool _repaidWholeLoan
    ) external onlyLendingPool {
        updateReserveStateOnRepayInternal(
            _reserve,
            _user,
            _paybackAmountMinusFees,
            _balanceIncrease
        );
        updateUserStateOnRepayInternal(
            _reserve,
            _user,
            _paybackAmountMinusFees,
            _originationFeeRepaid,
            _balanceIncrease,
            _repaidWholeLoan
        );

        updateReserveInterestRatesAndTimestampInternal(_reserve, _paybackAmountMinusFees, 0);
    }


/**
 * @dev updates the state of the core as a consequence of a swap rate action.
 * @param _reserve the address of the reserve on which the user is repaying
 * @param _user the address of the borrower
 * @param _principalBorrowBalance the amount borrowed by the user
 * @param _compoundedBorrowBalance the amount borrowed plus accrued interest
 * @param _balanceIncrease the accrued interest on the borrowed amount
 * @param _currentRateMode the current interest rate mode for the user
 **/

function updateStateOnSwapRate(
        address _reserve,
        address _user,
        uint256 _principalBorrowBalance,
        uint256 _compoundedBorrowBalance,
        uint256 _balanceIncrease,
        CoreLibrary.InterestRateMode _currentRateMode
    ) external onlyLendingPool returns (CoreLibrary.InterestRateMode, uint256) {
        updateReserveStateOnSwapRateInternal(
            _reserve,
            _user,
            _principalBorrowBalance,
            _compoundedBorrowBalance,
            _currentRateMode
        );

        CoreLibrary.InterestRateMode newRateMode = updateUserStateOnSwapRateInternal(
            _reserve,
            _user,
            _balanceIncrease,
            _currentRateMode
        );

        updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0);

        return (newRateMode, getUserCurrentBorrowRate(_reserve, _user));
    }


/**
 * @dev updates the state of the core as a consequence of a liquidation action.
 * @param _principalReserve the address of the principal reserve that is being repaid
 * @param _collateralReserve the address of the collateral reserve that is being liquidated
 * @param _user the address of the borrower
 * @param _amountToLiquidate the amount being repaid by the liquidator
 * @param _collateralToLiquidate the amount of collateral being liquidated
 * @param _feeLiquidated the amount of origination fee being liquidated
 * @param _liquidatedCollateralForFee the amount of collateral equivalent to the origination fee + bonus
 * @param _balanceIncrease the accrued interest on the borrowed amount
 * @param _liquidatorReceivesAToken true if the liquidator will receive aTokens, false otherwise
 **/

function updateStateOnLiquidation(
        address _principalReserve,
        address _collateralReserve,
        address _user,
        uint256 _amountToLiquidate,
        uint256 _collateralToLiquidate,
        uint256 _feeLiquidated,
        uint256 _liquidatedCollateralForFee,
        uint256 _balanceIncrease,
        bool _liquidatorReceivesAToken
    ) external onlyLendingPool {
        updatePrincipalReserveStateOnLiquidationInternal(
            _principalReserve,
            _user,
            _amountToLiquidate,
            _balanceIncrease
        );

        updateCollateralReserveStateOnLiquidationInternal(
            _collateralReserve
        );

        updateUserStateOnLiquidationInternal(
            _principalReserve,
            _user,
            _amountToLiquidate,
            _feeLiquidated,
            _balanceIncrease
        );

        updateReserveInterestRatesAndTimestampInternal(_principalReserve, _amountToLiquidate, 0);

        if (!_liquidatorReceivesAToken) {
            updateReserveInterestRatesAndTimestampInternal(
                _collateralReserve,
                0,
                _collateralToLiquidate.add(_liquidatedCollateralForFee)
            );
        }

    }


/**
 * @dev updates the state of the core as a consequence of a stable rate rebalance
 * @param _reserve the address of the principal reserve where the user borrowed
 * @param _user the address of the borrower
 * @param _balanceIncrease the accrued interest on the borrowed amount
 * @return the new stable rate for the user
 **/

function updateStateOnRebalance(address _reserve, address _user, uint256 _balanceIncrease)
        external
        onlyLendingPool
        returns (uint256)
    {
        updateReserveStateOnRebalanceInternal(_reserve, _user, _balanceIncrease);

        //update user data and rebalance the rate
        updateUserStateOnRebalanceInternal(_reserve, _user, _balanceIncrease);
        updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0);
        return usersReserveData[_user][_reserve].stableBorrowRate;
    }


/**
 * @dev transfers to the user a specific amount from the reserve.
 * @param _reserve the address of the reserve where the transfer is happening
 * @param _user the address of the user receiving the transfer
 * @param _amount the amount being transferred
 **/

function transferToUser(address _reserve, address payable _user, uint256 _amount)
        external
        onlyLendingPool
    {
        if (_reserve != EthAddressLib.ethAddress()) {
            ERC20(_reserve).safeTransfer(_user, _amount);
        } else {
            //solium-disable-next-line
            (bool result, ) = _user.call.value(_amount).gas(50000)("");
            require(result, "Transfer of ETH failed");
        }
    }


/**
 * @dev transfers the protocol fees to the fees collection address
 * @param _token the address of the token being transferred
 * @param _user the address of the user from where the transfer is happening
 * @param _amount the amount being transferred
 * @param _destination the fee receiver address
 **/

function transferToFeeCollectionAddress(
        address _token,
        address _user,
        uint256 _amount,
        address _destination
    ) external payable onlyLendingPool {
        address payable feeAddress = address(uint160(_destination)); //cast the address to payable

        if (_token != EthAddressLib.ethAddress()) {
            require(
                msg.value == 0,
                "User is sending ETH along with the ERC20 transfer. Check the value attribute of the transaction"
            );
            ERC20(_token).safeTransferFrom(_user, feeAddress, _amount);
        } else {
            require(msg.value >= _amount, "The amount and the value sent to deposit do not match");
            //solium-disable-next-line
            (bool result, ) = feeAddress.call.value(_amount).gas(50000)("");
            require(result, "Transfer of ETH failed");
        }
    }


/**
 * @dev transfers the fees to the fees collection address in the case of liquidation
 * @param _token the address of the token being transferred
 * @param _amount the amount being transferred
 * @param _destination the fee receiver address
 **/

function liquidateFee(
        address _token,
        uint256 _amount,
        address _destination
    ) external payable onlyLendingPool {
        address payable feeAddress = address(uint160(_destination)); //cast the address to payable
        require(
            msg.value == 0,
            "Fee liquidation does not require any transfer of value"
        );

        if (_token != EthAddressLib.ethAddress()) {
            ERC20(_token).safeTransfer(feeAddress, _amount);
        } else {
            //solium-disable-next-line
            (bool result, ) = feeAddress.call.value(_amount).gas(50000)("");
            require(result, "Transfer of ETH failed");
        }
    }


/**
 * @dev transfers an amount from a user to the destination reserve
 * @param _reserve the address of the reserve where the amount is being transferred
 * @param _user the address of the user from where the transfer is happening
 * @param _amount the amount being transferred
 **/

function transferToReserve(address _reserve, address payable _user, uint256 _amount)
        external
        payable
        onlyLendingPool
    {
        if (_reserve != EthAddressLib.ethAddress()) {
            require(msg.value == 0, "User is sending ETH along with the ERC20 transfer.");
            ERC20(_reserve).safeTransferFrom(_user, address(this), _amount);

        } else {
            require(msg.value >= _amount, "The amount and the value sent to deposit do not match");

            if (msg.value > _amount) {
                //send back excess ETH
                uint256 excessAmount = msg.value.sub(_amount);
                //solium-disable-next-line
                (bool result, ) = _user.call.value(excessAmount).gas(50000)("");
                require(result, "Transfer of ETH failed");
            }
        }
    }


/**
 * @dev returns the basic data (balances, fee accrued, reserve enabled/disabled as collateral)
 * needed to calculate the global account data in the LendingPoolDataProvider
 * @param _reserve the address of the reserve
 * @param _user the address of the user
 * @return the user deposited balance, the principal borrow balance, the fee, and if the reserve is enabled as collateral or not
 **/

function getUserBasicReserveData(address _reserve, address _user)
        external
        view
        returns (uint256, uint256, uint256, bool)
    {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];

        uint256 underlyingBalance = getUserUnderlyingAssetBalance(_reserve, _user);

        if (user.principalBorrowBalance == 0) {
            return (underlyingBalance, 0, 0, user.useAsCollateral);
        }

        return (
            underlyingBalance,
            user.getCompoundedBorrowBalance(reserve),
            user.originationFee,
            user.useAsCollateral
        );
    }


/**
 * @dev checks if a user is allowed to borrow at a stable rate
 * @param _reserve the reserve address
 * @param _user the user
 * @param _amount the amount the the user wants to borrow
 * @return true if the user is allowed to borrow at a stable rate, false otherwise
 **/

function isUserAllowedToBorrowAtStable(address _reserve, address _user, uint256 _amount)
        external
        view
        returns (bool)
    {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];

        if (!reserve.isStableBorrowRateEnabled) return false;

        return
            !user.useAsCollateral ||
            !reserve.usageAsCollateralEnabled ||
            _amount > getUserUnderlyingAssetBalance(_reserve, _user);
    }


/**
 * @dev gets the reserve current stable borrow rate. Is the market rate if the reserve is empty
 * @param _reserve the reserve address
 * @return the reserve current stable borrow rate
 **/

function getReserveCurrentStableBorrowRate(address _reserve) public view returns (uint256) {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        ILendingRateOracle oracle = ILendingRateOracle(addressesProvider.getLendingRateOracle());

        if (reserve.currentStableBorrowRate == 0) {
            //no stable rate borrows yet
            return oracle.getMarketBorrowRate(_reserve);
        }

        return reserve.currentStableBorrowRate;
    }


/**
 * @dev this function aggregates the configuration parameters of the reserve.
 * It's used in the LendingPoolDataProvider specifically to save gas, and avoid
 * multiple external contract calls to fetch the same data.
 * @param _reserve the reserve address
 * @return the reserve decimals
 * @return the base ltv as collateral
 * @return the liquidation threshold
 * @return if the reserve is used as collateral or not
 **/

function getReserveConfiguration(address _reserve)
        external
        view
        returns (uint256, uint256, uint256, bool)
    {
        uint256 decimals;
        uint256 baseLTVasCollateral;
        uint256 liquidationThreshold;
        bool usageAsCollateralEnabled;

        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        decimals = reserve.decimals;
        baseLTVasCollateral = reserve.baseLTVasCollateral;
        liquidationThreshold = reserve.liquidationThreshold;
        usageAsCollateralEnabled = reserve.usageAsCollateralEnabled;

        return (decimals, baseLTVasCollateral, liquidationThreshold, usageAsCollateralEnabled);
    }


/**
 * @dev returns the utilization rate U of a specific reserve
 * @param _reserve the reserve for which the information is needed
 * @return the utilization rate in ray
 **/

function getReserveUtilizationRate(address _reserve) public view returns (uint256) {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];

        uint256 totalBorrows = reserve.getTotalBorrows();

        if (totalBorrows == 0) {
            return 0;
        }

        uint256 availableLiquidity = getReserveAvailableLiquidity(_reserve);

        return totalBorrows.rayDiv(availableLiquidity.add(totalBorrows));
    }


/**
 * @dev users with no loans in progress have NONE as borrow rate mode
 * @param _reserve the address of the reserve for which the information is needed
 * @param _user the address of the user for which the information is needed
 * @return the borrow rate mode for the user,
 **/

function getUserCurrentBorrowRateMode(address _reserve, address _user)
        public
        view
        returns (CoreLibrary.InterestRateMode)
    {
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];

        if (user.principalBorrowBalance == 0) {
            return CoreLibrary.InterestRateMode.NONE;
        }

        return
            user.stableBorrowRate > 0
            ? CoreLibrary.InterestRateMode.STABLE
            : CoreLibrary.InterestRateMode.VARIABLE;
    }


/**
 * @dev gets the current borrow rate of the user
 * @param _reserve the address of the reserve for which the information is needed
 * @param _user the address of the user for which the information is needed
 * @return the borrow rate for the user,
 **/

function getUserCurrentBorrowRate(address _reserve, address _user)
        internal
        view
        returns (uint256)
    {
        CoreLibrary.InterestRateMode rateMode = getUserCurrentBorrowRateMode(_reserve, _user);

        if (rateMode == CoreLibrary.InterestRateMode.NONE) {
            return 0;
        }

        return
            rateMode == CoreLibrary.InterestRateMode.STABLE
            ? usersReserveData[_user][_reserve].stableBorrowRate
            : reserves[_reserve].currentVariableBorrowRate;
    }


/**
 * @dev calculates and returns the borrow balances of the user
 * @param _reserve the address of the reserve
 * @param _user the address of the user
 * @return the principal borrow balance, the compounded balance and the balance increase since the last borrow/repay/swap/rebalance
 **/

function getUserBorrowBalances(address _reserve, address _user)
        public
        view
        returns (uint256, uint256, uint256)
    {
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
        if (user.principalBorrowBalance == 0) {
            return (0, 0, 0);
        }

        uint256 principal = user.principalBorrowBalance;
        uint256 compoundedBalance = CoreLibrary.getCompoundedBorrowBalance(
            user,
            reserves[_reserve]
        );
        return (principal, compoundedBalance, compoundedBalance.sub(principal));
    }


/**
 * @dev removes the last added reserve in the reservesList array
 * @param _reserveToRemove the address of the reserve
 **/

function removeLastAddedReserve(address _reserveToRemove)
     external onlyLendingPoolConfigurator {

        address lastReserve = reservesList[reservesList.length-1];

        require(lastReserve == _reserveToRemove, "Reserve being removed is different than the reserve requested");

        //as we can't check if totalLiquidity is 0 (since the reserve added might not be an ERC20) we at least check that there is nothing borrowed
        require(getReserveTotalBorrows(lastReserve) == 0, "Cannot remove a reserve with liquidity deposited");

        reserves[lastReserve].isActive = false;
        reserves[lastReserve].aTokenAddress = address(0);
        reserves[lastReserve].decimals = 0;
        reserves[lastReserve].lastLiquidityCumulativeIndex = 0;
        reserves[lastReserve].lastVariableBorrowCumulativeIndex = 0;
        reserves[lastReserve].borrowingEnabled = false;
        reserves[lastReserve].usageAsCollateralEnabled = false;
        reserves[lastReserve].baseLTVasCollateral = 0;
        reserves[lastReserve].liquidationThreshold = 0;
        reserves[lastReserve].liquidationBonus = 0;
        reserves[lastReserve].interestRateStrategyAddress = address(0);

        reservesList.pop();
    }


/**
 * @dev updates the state of a reserve as a consequence of a borrow action.
 * @param _reserve the address of the reserve on which the user is borrowing
 * @param _user the address of the borrower
 * @param _principalBorrowBalance the previous borrow balance of the borrower before the action
 * @param _balanceIncrease the accrued interest of the user on the previous borrowed amount
 * @param _amountBorrowed the new amount borrowed
 * @param _rateMode the borrow rate mode (stable, variable)
 **/

function updateReserveStateOnBorrowInternal(
        address _reserve,
        address _user,
        uint256 _principalBorrowBalance,
        uint256 _balanceIncrease,
        uint256 _amountBorrowed,
        CoreLibrary.InterestRateMode _rateMode
    ) internal {
        reserves[_reserve].updateCumulativeIndexes();

        //increasing reserve total borrows to account for the new borrow balance of the user
        //NOTE: Depending on the previous borrow mode, the borrows might need to be switched from variable to stable or vice versa

        updateReserveTotalBorrowsByRateModeInternal(
            _reserve,
            _user,
            _principalBorrowBalance,
            _balanceIncrease,
            _amountBorrowed,
            _rateMode
        );
    }


/**
 * @dev updates the state of the reserve as a consequence of a repay action.
 * @param _reserve the address of the reserve on which the user is repaying
 * @param _user the address of the borrower
 * @param _paybackAmountMinusFees the amount being paid back minus fees
 * @param _balanceIncrease the accrued interest on the borrowed amount
 **/

function updateReserveStateOnRepayInternal(
        address _reserve,
        address _user,
        uint256 _paybackAmountMinusFees,
        uint256 _balanceIncrease
    ) internal {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];

        CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode(_reserve, _user);

        //update the indexes
        reserves[_reserve].updateCumulativeIndexes();

        //compound the cumulated interest to the borrow balance and then subtracting the payback amount
        if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) {
            reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                _balanceIncrease,
                user.stableBorrowRate
            );
            reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                _paybackAmountMinusFees,
                user.stableBorrowRate
            );
        } else {
            reserve.increaseTotalBorrowsVariable(_balanceIncrease);
            reserve.decreaseTotalBorrowsVariable(_paybackAmountMinusFees);
        }
    }


/**
 * @dev updates the state of the principal reserve as a consequence of a liquidation action.
 * @param _principalReserve the address of the principal reserve that is being repaid
 * @param _user the address of the borrower
 * @param _amountToLiquidate the amount being repaid by the liquidator
 * @param _balanceIncrease the accrued interest on the borrowed amount
 **/

function updatePrincipalReserveStateOnLiquidationInternal(
        address _principalReserve,
        address _user,
        uint256 _amountToLiquidate,
        uint256 _balanceIncrease
    ) internal {
        CoreLibrary.ReserveData storage reserve = reserves[_principalReserve];
        CoreLibrary.UserReserveData storage user = usersReserveData[_user][_principalReserve];

        //update principal reserve data
        reserve.updateCumulativeIndexes();

        CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode(
            _principalReserve,
            _user
        );

        if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) {
            //increase the total borrows by the compounded interest
            reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                _balanceIncrease,
                user.stableBorrowRate
            );

            //decrease by the actual amount to liquidate
            reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                _amountToLiquidate,
                user.stableBorrowRate
            );

        } else {
            //increase the total borrows by the compounded interest
            reserve.increaseTotalBorrowsVariable(_balanceIncrease);

            //decrease by the actual amount to liquidate
            reserve.decreaseTotalBorrowsVariable(_amountToLiquidate);
        }

    }


/**
 * @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl.
 * Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information.
 * @param _reserve the address of the reserve to be updated
 * @param _liquidityAdded the amount of liquidity added to the protocol (deposit or repay) in the previous action
 * @param _liquidityTaken the amount of liquidity taken from the protocol (redeem or borrow)
 **/

function updateReserveInterestRatesAndTimestampInternal(
        address _reserve,
        uint256 _liquidityAdded,
        uint256 _liquidityTaken
    ) internal {
        CoreLibrary.ReserveData storage reserve = reserves[_reserve];
        (uint256 newLiquidityRate, uint256 newStableRate, uint256 newVariableRate) = IReserveInterestRateStrategy(
            reserve
                .interestRateStrategyAddress
        )
            .calculateInterestRates(
            _reserve,
            getReserveAvailableLiquidity(_reserve).add(_liquidityAdded).sub(_liquidityTaken),
            reserve.totalBorrowsStable,
            reserve.totalBorrowsVariable,
            reserve.currentAverageStableBorrowRate
        );

        reserve.currentLiquidityRate = newLiquidityRate;
        reserve.currentStableBorrowRate = newStableRate;
        reserve.currentVariableBorrowRate = newVariableRate;

        //solium-disable-next-line
        reserve.lastUpdateTimestamp = uint40(block.timestamp);

        emit ReserveUpdated(
            _reserve,
            newLiquidityRate,
            newStableRate,
            newVariableRate,
            reserve.lastLiquidityCumulativeIndex,
            reserve.lastVariableBorrowCumulativeIndex
        );
    }


/**
 * @dev transfers to the protocol fees of a flashloan to the fees collection address
 * @param _token the address of the token being transferred
 * @param _amount the amount being transferred
 **/

function transferFlashLoanProtocolFeeInternal(address _token, uint256 _amount) internal {
        address payable receiver = address(uint160(addressesProvider.getTokenDistributor()));

        if (_token != EthAddressLib.ethAddress()) {
            ERC20(_token).safeTransfer(receiver, _amount);
        } else {
            //solium-disable-next-line
            (bool result, ) = receiver.call.value(_amount)("");
            require(result, "Transfer to token distributor failed");
        }
    }


/**
 * @dev adds a reserve to the array of the reserves address
 **/

function addReserveToListInternal(address _reserve) internal {
        bool reserveAlreadyAdded = false;
        for (uint256 i = 0; i < reservesList.length; i++)
            if (reservesList[i] == _reserve) {
                reserveAlreadyAdded = true;
            }
        if (!reserveAlreadyAdded) reservesList.push(_reserve);
    }


/**
 * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens)
 * is minted.
 * @param _reserve the address of the reserve
 * @param _amount the amount to be deposited
 * @param _referralCode integrators are assigned a referral code and can potentially receive rewards.
 **/

function deposit(address _reserve, uint256 _amount, uint16 _referralCode)
        external
        payable
        nonReentrant
        onlyActiveReserve(_reserve)
        onlyUnfreezedReserve(_reserve)
        onlyAmountGreaterThanZero(_amount)
    {
        AToken aToken = AToken(core.getReserveATokenAddress(_reserve));

        bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0;

        core.updateStateOnDeposit(_reserve, msg.sender, _amount, isFirstDeposit);

        //minting AToken to user 1:1 with the specific exchange rate
        aToken.mintOnDeposit(msg.sender, _amount);

        //transfer to the core contract
        core.transferToReserve.value(msg.value)(_reserve, msg.sender, _amount);

        //solium-disable-next-line
        emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp);

    }


/**
 * @dev Redeems the underlying amount of assets requested by _user.
 * This function is executed by the overlying aToken contract in response to a redeem action.
 * @param _reserve the address of the reserve
 * @param _user the address of the user performing the action
 * @param _amount the underlying amount to be redeemed
 **/

function redeemUnderlying(
        address _reserve,
        address payable _user,
        uint256 _amount,
        uint256 _aTokenBalanceAfterRedeem
    )
        external
        nonReentrant
        onlyOverlyingAToken(_reserve)
        onlyActiveReserve(_reserve)
        onlyAmountGreaterThanZero(_amount)
    {
        uint256 currentAvailableLiquidity = core.getReserveAvailableLiquidity(_reserve);
        require(
            currentAvailableLiquidity >= _amount,
            "There is not enough liquidity available to redeem"
        );

        core.updateStateOnRedeem(_reserve, _user, _amount, _aTokenBalanceAfterRedeem == 0);

        core.transferToUser(_reserve, _user, _amount);

        //solium-disable-next-line
        emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp);

    }


/**
 * @dev borrowers can user this function to swap between stable and variable borrow rate modes.
 * @param _reserve the address of the reserve on which the user borrowed
 **/

function swapBorrowRateMode(address _reserve)
        external
        nonReentrant
        onlyActiveReserve(_reserve)
        onlyUnfreezedReserve(_reserve)
    {
        (uint256 principalBorrowBalance, uint256 compoundedBorrowBalance, uint256 borrowBalanceIncrease) = core
            .getUserBorrowBalances(_reserve, msg.sender);

        require(
            compoundedBorrowBalance > 0,
            "User does not have a borrow in progress on this reserve"
        );

        CoreLibrary.InterestRateMode currentRateMode = core.getUserCurrentBorrowRateMode(
            _reserve,
            msg.sender
        );

        if (currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
            /**
            * user wants to swap to stable, before swapping we need to ensure that
            * 1. stable borrow rate is enabled on the reserve
            * 2. user is not trying to abuse the reserve by depositing
            * more collateral than he is borrowing, artificially lowering
            * the interest rate, borrowing at variable, and switching to stable
            **/
            require(
                core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, compoundedBorrowBalance),
                "User cannot borrow the selected amount at stable"
            );
        }

        (CoreLibrary.InterestRateMode newRateMode, uint256 newBorrowRate) = core
            .updateStateOnSwapRate(
            _reserve,
            msg.sender,
            principalBorrowBalance,
            compoundedBorrowBalance,
            borrowBalanceIncrease,
            currentRateMode
        );

        emit Swap(
            _reserve,
            msg.sender,
            uint256(newRateMode),
            newBorrowRate,
            borrowBalanceIncrease,
            //solium-disable-next-line
            block.timestamp
        );
    }


/**
 * @dev allows depositors to enable or disable a specific deposit as collateral.
 * @param _reserve the address of the reserve
 * @param _useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
 **/

function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral)
        external
        nonReentrant
        onlyActiveReserve(_reserve)
        onlyUnfreezedReserve(_reserve)
    {
        uint256 underlyingBalance = core.getUserUnderlyingAssetBalance(_reserve, msg.sender);

        require(underlyingBalance > 0, "User does not have any liquidity deposited");

        require(
            dataProvider.balanceDecreaseAllowed(_reserve, msg.sender, underlyingBalance),
            "User deposit is already being used as collateral"
        );

        core.setUserUseReserveAsCollateral(_reserve, msg.sender, _useAsCollateral);

        if (_useAsCollateral) {
            emit ReserveUsedAsCollateralEnabled(_reserve, msg.sender);
        } else {
            emit ReserveUsedAsCollateralDisabled(_reserve, msg.sender);
        }
    }


/**
 * @dev users can invoke this function to liquidate an undercollateralized position.
 * @param _reserve the address of the collateral to liquidated
 * @param _reserve the address of the principal reserve
 * @param _user the address of the borrower
 * @param _purchaseAmount the amount of principal that the liquidator wants to repay
 * @param _receiveAToken true if the liquidators wants to receive the aTokens, false if
 * he wants to receive the underlying asset directly
 **/

function liquidationCall(
        address _collateral,
        address _reserve,
        address _user,
        uint256 _purchaseAmount,
        bool _receiveAToken
    ) external payable nonReentrant onlyActiveReserve(_reserve) onlyActiveReserve(_collateral) {
        address liquidationManager = addressesProvider.getLendingPoolLiquidationManager();

        //solium-disable-next-line
        (bool success, bytes memory result) = liquidationManager.delegatecall(
            abi.encodeWithSignature(
                "liquidationCall(address,address,address,uint256,bool)",
                _collateral,
                _reserve,
                _user,
                _purchaseAmount,
                _receiveAToken
            )
        );
        require(success, "Liquidation call failed");

        (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));

        if (returnCode != 0) {
            //error found
            revert(string(abi.encodePacked("Liquidation failed: ", returnMessage)));
        }
    }


/**
 * @dev calculates how much of a specific collateral can be liquidated, given
 * a certain amount of principal currency. This function needs to be called after
 * all the checks to validate the liquidation have been performed, otherwise it might fail.
 * @param _collateral the collateral to be liquidated
 * @param _principal the principal currency to be liquidated
 * @param _purchaseAmount the amount of principal being liquidated
 * @param _userCollateralBalance the collatera balance for the specific _collateral asset of the user being liquidated
 * @return the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor) and
 * the purchase amount
 **/

function calculateAvailableCollateralToLiquidate(
        address _collateral,
        address _principal,
        uint256 _purchaseAmount,
        uint256 _userCollateralBalance
    ) internal view returns (uint256 collateralAmount, uint256 principalAmountNeeded) {
        collateralAmount = 0;
        principalAmountNeeded = 0;
        IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());

        // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
        AvailableCollateralToLiquidateLocalVars memory vars;

        vars.collateralPrice = oracle.getAssetPrice(_collateral);
        vars.principalCurrencyPrice = oracle.getAssetPrice(_principal);
        vars.liquidationBonus = core.getReserveLiquidationBonus(_collateral);

        //this is the maximum possible amount of the selected collateral that can be liquidated, given the
        //max amount of principal currency that is available for liquidation.
        vars.maxAmountCollateralToLiquidate = vars
            .principalCurrencyPrice
            .mul(_purchaseAmount)
            .div(vars.collateralPrice)
            .mul(vars.liquidationBonus)
            .div(100);

        if (vars.maxAmountCollateralToLiquidate > _userCollateralBalance) {
            collateralAmount = _userCollateralBalance;
            principalAmountNeeded = vars
                .collateralPrice
                .mul(collateralAmount)
                .div(vars.principalCurrencyPrice)
                .mul(100)
                .div(vars.liquidationBonus);
        } else {
            collateralAmount = vars.maxAmountCollateralToLiquidate;
            principalAmountNeeded = _purchaseAmount;
        }

        return (collateralAmount, principalAmountNeeded);
    }


/**
 @dev Increase a collateral debt ceiling. Amount will be converted to the correct internal precision.
 @param _ilk    The ilk to update (ex. bytes32("ETH-A"))
 @param _amount The amount to increase in DAI (ex. 10m DAI amount == 10000000)
 @param _global If true, increases the global debt ceiling by _amount
 */

function increaseIlkDebtCeiling(bytes32 _ilk, uint256 _amount, bool _global) public {
        require(_amount < WAD);  // "LibDssExec/incorrect-ilk-line-precision"
        address _vat = vat();
        (,,,uint256 line_,) = DssVat(_vat).ilks(_ilk);
        Fileable(_vat).file(_ilk, "line", add(line_, _amount * RAD));
        if (_global) { increaseGlobalDebtCeiling(_amount); }
    }


/**
 @dev Set the debt ceiling for an ilk in the "MCD_IAM_AUTO_LINE" auto-line without updating the time values
 @param _ilk    The ilk to update (ex. bytes32("ETH-A"))
 @param _amount The amount to decrease in DAI (ex. 10m DAI amount == 10000000)
 */

function setIlkAutoLineDebtCeiling(bytes32 _ilk, uint256 _amount) public {
        address _autoLine = autoLine();
        (, uint256 gap, uint48 ttl,,) = IAMLike(_autoLine).ilks(_ilk);
        require(gap != 0 && ttl != 0);  // "LibDssExec/auto-line-not-configured"
        IAMLike(_autoLine).setIlk(_ilk, _amount * RAD, uint256(gap), uint256(ttl));
    }


/**
 @dev Set a collateral liquidation ratio. Amount will be converted to the correct internal precision.
 @dev Equation used for conversion is pct * RAY / 10,000
 @param _ilk    The ilk to update (ex. bytes32("ETH-A"))
 @param _pct_bps    The pct, in basis points, to set in integer form (x100). (ex. 150% = 150 * 100 = 15000)
 */

function setIlkLiquidationRatio(bytes32 _ilk, uint256 _pct_bps) public {
        require(_pct_bps < 10 * BPS_ONE_HUNDRED_PCT); // "LibDssExec/incorrect-ilk-mat-precision" // Fails if pct >= 1000%
        require(_pct_bps >= BPS_ONE_HUNDRED_PCT); // the liquidation ratio has to be bigger or equal to 100%
        Fileable(spotter()).file(_ilk, "mat", rdiv(_pct_bps, BPS_ONE_HUNDRED_PCT));
    }


/**
 * @dev Configures the distribution of rewards for a list of assets
 * @param assetsConfigInput The list of configurations to apply
 **/

function configureAssets(DistributionTypes.AssetConfigInput[] calldata assetsConfigInput)
    external
    override
  {
    require(msg.sender == EMISSION_MANAGER, 'ONLY_EMISSION_MANAGER');

    for (uint256 i = 0; i < assetsConfigInput.length; i++) {
      AssetData storage assetConfig = assets[assetsConfigInput[i].underlyingAsset];

      _updateAssetStateInternal(
        assetsConfigInput[i].underlyingAsset,
        assetConfig,
        assetsConfigInput[i].totalStaked
      );

      assetConfig.emissionPerSecond = assetsConfigInput[i].emissionPerSecond;

      emit AssetConfigUpdated(
        assetsConfigInput[i].underlyingAsset,
        assetsConfigInput[i].emissionPerSecond
      );
    }
  }


/**
 * @dev Updates the state of one distribution, mainly rewards index and timestamp
 * @param underlyingAsset The address used as key in the distribution, for example sAAVE or the aTokens addresses on Aave
 * @param assetConfig Storage pointer to the distribution's config
 * @param totalStaked Current total of staked assets for this distribution
 * @return The new distribution index
 **/

function _updateAssetStateInternal(
    address underlyingAsset,
    AssetData storage assetConfig,
    uint256 totalStaked
  ) internal returns (uint256) {
    uint256 oldIndex = assetConfig.index;
    uint128 lastUpdateTimestamp = assetConfig.lastUpdateTimestamp;

    if (block.timestamp == lastUpdateTimestamp) {
      return oldIndex;
    }

    uint256 newIndex =
      _getAssetIndex(oldIndex, assetConfig.emissionPerSecond, lastUpdateTimestamp, totalStaked);

    if (newIndex != oldIndex) {
      assetConfig.index = newIndex;
      emit AssetIndexUpdated(underlyingAsset, newIndex);
    }

    assetConfig.lastUpdateTimestamp = uint128(block.timestamp);

    return newIndex;
  }


/**
 * @dev Updates the state of an user in a distribution
 * @param user The user's address
 * @param asset The address of the reference asset of the distribution
 * @param stakedByUser Amount of tokens staked by the user in the distribution at the moment
 * @param totalStaked Total tokens staked in the distribution
 * @return The accrued rewards for the user until the moment
 **/

function _updateUserAssetInternal(
    address user,
    address asset,
    uint256 stakedByUser,
    uint256 totalStaked
  ) internal returns (uint256) {
    AssetData storage assetData = assets[asset];
    uint256 userIndex = assetData.users[user];
    uint256 accruedRewards = 0;

    uint256 newIndex = _updateAssetStateInternal(asset, assetData, totalStaked);

    if (userIndex != newIndex) {
      if (stakedByUser != 0) {
        accruedRewards = _getRewards(stakedByUser, newIndex, userIndex);
      }

      assetData.users[user] = newIndex;
      emit UserIndexUpdated(user, asset, newIndex);
    }

    return accruedRewards;
  }


/**
 * @dev Used by "frontend" stake contracts to update the data of an user when claiming rewards from there
 * @param user The address of the user
 * @param stakes List of structs of the user data related with his stake
 * @return The accrued rewards for the user until the moment
 **/

function _claimRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
    internal
    returns (uint256)
  {
    uint256 accruedRewards = 0;

    for (uint256 i = 0; i < stakes.length; i++) {
      accruedRewards = accruedRewards.add(
        _updateUserAssetInternal(
          user,
          stakes[i].underlyingAsset,
          stakes[i].stakedByUser,
          stakes[i].totalStaked
        )
      );
    }

    return accruedRewards;
  }


/**
 * @dev Return the accrued rewards for an user over a list of distribution
 * @param user The address of the user
 * @param stakes List of structs of the user data related with his stake
 * @return The accrued rewards for the user until the moment
 **/

function _getUnclaimedRewards(address user, DistributionTypes.UserStakeInput[] memory stakes)
    internal
    view
    returns (uint256)
  {
    uint256 accruedRewards = 0;

    for (uint256 i = 0; i < stakes.length; i++) {
      AssetData storage assetConfig = assets[stakes[i].underlyingAsset];
      uint256 assetIndex =
        _getAssetIndex(
          assetConfig.index,
          assetConfig.emissionPerSecond,
          assetConfig.lastUpdateTimestamp,
          stakes[i].totalStaked
        );

      accruedRewards = accruedRewards.add(
        _getRewards(stakes[i].stakedByUser, assetIndex, assetConfig.users[user])
      );
    }
    return accruedRewards;
  }


/**
 * @dev Calculates the next value of an specific distribution index, with validations
 * @param currentIndex Current index of the distribution
 * @param emissionPerSecond Representing the total rewards distributed per second per asset unit, on the distribution
 * @param lastUpdateTimestamp Last moment this distribution was updated
 * @param totalBalance of tokens considered for the distribution
 * @return The new index.
 **/

function _getAssetIndex(
    uint256 currentIndex,
    uint256 emissionPerSecond,
    uint128 lastUpdateTimestamp,
    uint256 totalBalance
  ) internal view returns (uint256) {
    if (
      emissionPerSecond == 0 ||
      totalBalance == 0 ||
      lastUpdateTimestamp == block.timestamp ||
      lastUpdateTimestamp >= DISTRIBUTION_END
    ) {
      return currentIndex;
    }

    uint256 currentTimestamp =
      block.timestamp > DISTRIBUTION_END ? DISTRIBUTION_END : block.timestamp;
    uint256 timeDelta = currentTimestamp.sub(lastUpdateTimestamp);
    return
      emissionPerSecond.mul(timeDelta).mul(10**uint256(PRECISION)).div(totalBalance).add(
        currentIndex
      );
  }


/**
 * @dev returns the current delegated power of a user. The current power is the
 * power delegated at the time of the last snapshot
 * @param user the user
 **/

function getPowerCurrent(address user, DelegationType delegationType)
    external
    override
    view
    returns (uint256)
  {
    (
      mapping(address => mapping(uint256 => Snapshot)) storage snapshots,
      mapping(address => uint256) storage snapshotsCounts,

    ) = _getDelegationDataByType(delegationType);

    return _searchByBlockNumber(snapshots, snapshotsCounts, user, block.number);
  }


/**
 * @dev returns the delegated power of a user at a certain block
 * @param user the user
 **/

function getPowerAtBlock(
    address user,
    uint256 blockNumber,
    DelegationType delegationType
  ) external override view returns (uint256) {
    (
      mapping(address => mapping(uint256 => Snapshot)) storage snapshots,
      mapping(address => uint256) storage snapshotsCounts,

    ) = _getDelegationDataByType(delegationType);

    return _searchByBlockNumber(snapshots, snapshotsCounts, user, blockNumber);
  }


/**
 * @dev delegates the specific power to a delegatee
 * @param delegatee the user which delegated power has changed
 * @param delegationType the type of delegation (VOTING_POWER, PROPOSITION_POWER)
 **/

function _delegateByType(
    address delegator,
    address delegatee,
    DelegationType delegationType
  ) internal {
    require(delegatee != address(0), 'INVALID_DELEGATEE');

    (, , mapping(address => address) storage delegates) = _getDelegationDataByType(delegationType);

    uint256 delegatorBalance = balanceOf(delegator);

    address previousDelegatee = _getDelegatee(delegator, delegates);

    delegates[delegator] = delegatee;

    _moveDelegatesByType(previousDelegatee, delegatee, delegatorBalance, delegationType);
    emit DelegateChanged(delegator, delegatee, delegationType);
  }


/**
 * @dev moves delegated power from one user to another
 * @param from the user from which delegated power is moved
 * @param to the user that will receive the delegated power
 * @param amount the amount of delegated power to be moved
 * @param delegationType the type of delegation (VOTING_POWER, PROPOSITION_POWER)
 **/

function _moveDelegatesByType(
    address from,
    address to,
    uint256 amount,
    DelegationType delegationType
  ) internal {
    if (from == to) {
      return;
    }

    (
      mapping(address => mapping(uint256 => Snapshot)) storage snapshots,
      mapping(address => uint256) storage snapshotsCounts,

    ) = _getDelegationDataByType(delegationType);

    if (from != address(0)) {
      uint256 previous = 0;
      uint256 fromSnapshotsCount = snapshotsCounts[from];

      if (fromSnapshotsCount != 0) {
        previous = snapshots[from][fromSnapshotsCount - 1].value;
      } else {
        previous = balanceOf(from);
      }

      _writeSnapshot(
        snapshots,
        snapshotsCounts,
        from,
        uint128(previous),
        uint128(previous.sub(amount))
      );

      emit DelegatedPowerChanged(from, previous.sub(amount), delegationType);
    }
    if (to != address(0)) {
      uint256 previous = 0;
      uint256 toSnapshotsCount = snapshotsCounts[to];
      if (toSnapshotsCount != 0) {
        previous = snapshots[to][toSnapshotsCount - 1].value;
      } else {
        previous = balanceOf(to);
      }

      _writeSnapshot(
        snapshots,
        snapshotsCounts,
        to,
        uint128(previous),
        uint128(previous.add(amount))
      );

      emit DelegatedPowerChanged(to, previous.add(amount), delegationType);
    }
  }


/**
 * @dev searches a snapshot by block number. Uses binary search.
 * @param snapshots the snapshots mapping
 * @param snapshotsCounts the number of snapshots
 * @param user the user for which the snapshot is being searched
 * @param blockNumber the block number being searched
 **/

function _searchByBlockNumber(
    mapping(address => mapping(uint256 => Snapshot)) storage snapshots,
    mapping(address => uint256) storage snapshotsCounts,
    address user,
    uint256 blockNumber
  ) internal view returns (uint256) {
    require(blockNumber <= block.number, 'INVALID_BLOCK_NUMBER');

    uint256 snapshotsCount = snapshotsCounts[user];

    if (snapshotsCount == 0) {
      return balanceOf(user);
    }

    // First check most recent balance
    if (snapshots[user][snapshotsCount - 1].blockNumber <= blockNumber) {
      return snapshots[user][snapshotsCount - 1].value;
    }

    // Next check implicit zero balance
    if (snapshots[user][0].blockNumber > blockNumber) {
      return 0;
    }

    uint256 lower = 0;
    uint256 upper = snapshotsCount - 1;
    while (upper > lower) {
      uint256 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
      Snapshot memory snapshot = snapshots[user][center];
      if (snapshot.blockNumber == blockNumber) {
        return snapshot.value;
      } else if (snapshot.blockNumber < blockNumber) {
        lower = center;
      } else {
        upper = center - 1;
      }
    }
    return snapshots[user][lower].value;
  }


/**
 * @dev Writes a snapshot for an owner of tokens
 * @param owner The owner of the tokens
 * @param oldValue The value before the operation that is gonna be executed after the snapshot
 * @param newValue The value after the operation
 */

function _writeSnapshot(
    mapping(address => mapping(uint256 => Snapshot)) storage snapshots,
    mapping(address => uint256) storage snapshotsCounts,
    address owner,
    uint128 oldValue,
    uint128 newValue
  ) internal {
    uint128 currentBlock = uint128(block.number);

    uint256 ownerSnapshotsCount = snapshotsCounts[owner];
    mapping(uint256 => Snapshot) storage snapshotsOwner = snapshots[owner];

    // Doing multiple operations in the same block
    if (
      ownerSnapshotsCount != 0 &&
      snapshotsOwner[ownerSnapshotsCount - 1].blockNumber == currentBlock
    ) {
      snapshotsOwner[ownerSnapshotsCount - 1].value = newValue;
    } else {
      snapshotsOwner[ownerSnapshotsCount] = Snapshot(currentBlock, newValue);
      snapshotsCounts[owner] = ownerSnapshotsCount + 1;
    }
  }


/**
 * @dev Redeems staked tokens, and stop earning rewards
 * @param to Address to redeem to
 * @param amount Amount to redeem
 **/

function redeem(address to, uint256 amount) external override {
    require(amount != 0, 'INVALID_ZERO_AMOUNT');
    //solium-disable-next-line
    uint256 cooldownStartTimestamp = stakersCooldowns[msg.sender];
    require(
      block.timestamp > cooldownStartTimestamp.add(COOLDOWN_SECONDS),
      'INSUFFICIENT_COOLDOWN'
    );
    require(
      block.timestamp.sub(cooldownStartTimestamp.add(COOLDOWN_SECONDS)) <= UNSTAKE_WINDOW,
      'UNSTAKE_WINDOW_FINISHED'
    );
    uint256 balanceOfMessageSender = balanceOf(msg.sender);

    uint256 amountToRedeem = (amount > balanceOfMessageSender) ? balanceOfMessageSender : amount;

    _updateCurrentUnclaimedRewards(msg.sender, balanceOfMessageSender, true);

    _burn(msg.sender, amountToRedeem);

    if (balanceOfMessageSender.sub(amountToRedeem) == 0) {
      stakersCooldowns[msg.sender] = 0;
    }

    IERC20(STAKED_TOKEN).safeTransfer(to, amountToRedeem);

    emit Redeem(msg.sender, to, amountToRedeem);
  }


/**
 * @dev Claims an `amount` of `REWARD_TOKEN` to the address `to`
 * @param to Address to stake for
 * @param amount Amount to stake
 **/

function claimRewards(address to, uint256 amount) external override {
    uint256 newTotalRewards =
      _updateCurrentUnclaimedRewards(msg.sender, balanceOf(msg.sender), false);
    uint256 amountToClaim = (amount == type(uint256).max) ? newTotalRewards : amount;

    stakerRewardsToClaim[msg.sender] = newTotalRewards.sub(amountToClaim, 'INVALID_AMOUNT');

    REWARD_TOKEN.safeTransferFrom(REWARDS_VAULT, to, amountToClaim);

    emit RewardsClaimed(msg.sender, to, amountToClaim);
  }


/**
 * @dev Internal ERC20 _transfer of the tokenized staked tokens
 * @param from Address to transfer from
 * @param to Address to transfer to
 * @param amount Amount to transfer
 **/

function _transfer(
    address from,
    address to,
    uint256 amount
  ) internal override {
    uint256 balanceOfFrom = balanceOf(from);
    // Sender
    _updateCurrentUnclaimedRewards(from, balanceOfFrom, true);

    // Recipient
    if (from != to) {
      uint256 balanceOfTo = balanceOf(to);
      _updateCurrentUnclaimedRewards(to, balanceOfTo, true);

      uint256 previousSenderCooldown = stakersCooldowns[from];
      stakersCooldowns[to] = getNextCooldownTimestamp(
        previousSenderCooldown,
        amount,
        to,
        balanceOfTo
      );
      // if cooldown was set and whole balance of sender was transferred - clear cooldown
      if (balanceOfFrom == amount && previousSenderCooldown != 0) {
        stakersCooldowns[from] = 0;
      }
    }

    super._transfer(from, to, amount);
  }


/**
 * @dev Updates the user state related with his accrued rewards
 * @param user Address of the user
 * @param userBalance The current balance of the user
 * @param updateStorage Boolean flag used to update or not the stakerRewardsToClaim of the user
 * @return The unclaimed rewards that were added to the total accrued
 **/

function _updateCurrentUnclaimedRewards(
    address user,
    uint256 userBalance,
    bool updateStorage
  ) internal returns (uint256) {
    uint256 accruedRewards =
      _updateUserAssetInternal(user, address(this), userBalance, totalSupply());
    uint256 unclaimedRewards = stakerRewardsToClaim[user].add(accruedRewards);

    if (accruedRewards != 0) {
      if (updateStorage) {
        stakerRewardsToClaim[user] = unclaimedRewards;
      }
      emit RewardsAccrued(user, accruedRewards);
    }

    return unclaimedRewards;
  }


/**
 * @dev Calculates the how is gonna be a new cooldown timestamp depending on the sender/receiver situation
 *  - If the timestamp of the sender is "better" or the timestamp of the recipient is 0, we take the one of the recipient
 *  - Weighted average of from/to cooldown timestamps if:
 *    # The sender doesn't have the cooldown activated (timestamp 0).
 *    # The sender timestamp is expired
 *    # The sender has a "worse" timestamp
 *  - If the receiver's cooldown timestamp expired (too old), the next is 0
 * @param fromCooldownTimestamp Cooldown timestamp of the sender
 * @param amountToReceive Amount
 * @param toAddress Address of the recipient
 * @param toBalance Current balance of the receiver
 * @return The new cooldown timestamp
 **/

function getNextCooldownTimestamp(
    uint256 fromCooldownTimestamp,
    uint256 amountToReceive,
    address toAddress,
    uint256 toBalance
  ) public returns (uint256) {
    uint256 toCooldownTimestamp = stakersCooldowns[toAddress];
    if (toCooldownTimestamp == 0) {
      return 0;
    }

    uint256 minimalValidCooldownTimestamp =
      block.timestamp.sub(COOLDOWN_SECONDS).sub(UNSTAKE_WINDOW);

    if (minimalValidCooldownTimestamp > toCooldownTimestamp) {
      toCooldownTimestamp = 0;
    } else {
      uint256 fromCooldownTimestamp =
        (minimalValidCooldownTimestamp > fromCooldownTimestamp)
          ? block.timestamp
          : fromCooldownTimestamp;

      if (fromCooldownTimestamp < toCooldownTimestamp) {
        return toCooldownTimestamp;
      } else {
        toCooldownTimestamp = (
          amountToReceive.mul(fromCooldownTimestamp).add(toBalance.mul(toCooldownTimestamp))
        )
          .div(amountToReceive.add(toBalance));
      }
    }
    stakersCooldowns[toAddress] = toCooldownTimestamp;

    return toCooldownTimestamp;
  }


/**
 * @dev Writes a snapshot before any operation involving transfer of value: _transfer, _mint and _burn
 * - On _transfer, it writes snapshots for both "from" and "to"
 * - On _mint, only for _to
 * - On _burn, only for _from
 * @param from the from address
 * @param to the to address
 * @param amount the amount to transfer
 */

function _beforeTokenTransfer(
    address from,
    address to,
    uint256 amount
  ) internal override {
    address votingFromDelegatee = _votingDelegates[from];
    address votingToDelegatee = _votingDelegates[to];

    if (votingFromDelegatee == address(0)) {
      votingFromDelegatee = from;
    }
    if (votingToDelegatee == address(0)) {
      votingToDelegatee = to;
    }

    _moveDelegatesByType(
      votingFromDelegatee,
      votingToDelegatee,
      amount,
      DelegationType.VOTING_POWER
    );

    address propPowerFromDelegatee = _propositionPowerDelegates[from];
    address propPowerToDelegatee = _propositionPowerDelegates[to];

    if (propPowerFromDelegatee == address(0)) {
      propPowerFromDelegatee = from;
    }
    if (propPowerToDelegatee == address(0)) {
      propPowerToDelegatee = to;
    }

    _moveDelegatesByType(
      propPowerFromDelegatee,
      propPowerToDelegatee,
      amount,
      DelegationType.PROPOSITION_POWER
    );

    // caching the aave governance address to avoid multiple state loads
    ITransferHook aaveGovernance = _aaveGovernance;
    if (aaveGovernance != ITransferHook(0)) {
      aaveGovernance.onTransfer(from, to, amount);
    }
  }


// Transfer the balance from token owner account to receiver account

function transfer(address to, uint tokens) public returns (bool success) {
        require(to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead
        require(!transactionLock);  // Check for transaction lock
        require(!frozenAccount[to]);// Check if recipient is frozen
        balances[msg.sender] = balances[msg.sender].sub(tokens);
        balances[to] = balances[to].add(tokens);
        emit Transfer(msg.sender, to, tokens);
        return true;
    }


// Transfer token from spender account to receiver account

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        require(to != 0x0); // Prevent transfer to 0x0 address. Use burn() instead
        require(!transactionLock);         // Check for transaction lock
        require(!frozenAccount[from]);     // Check if sender is frozen
        require(!frozenAccount[to]);       // Check if recipient is frozen
        balances[from] = balances[from].sub(tokens);
        allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
        balances[to] = balances[to].add(tokens);
        emit Transfer(from, to, tokens);
        return true;
    }


/**
 * @dev stakeNft allows a user to submit their NFT to the contract and begin getting returns.
 * @param _nftIds The ID of the NFT being staked.
 **/

function batchStakeNft(uint256[] memory _nftIds)
      public
      // doKeep
    {
        // Loop through all submitted NFT IDs and stake them.
        for (uint256 i = 0; i < _nftIds.length; i++) {
            _stake(_nftIds[i], msg.sender);
        }
    }


/**
 * @dev Internal function for staking--this allows us to skip updating stake multiple times during a batch stake.
 * @param _nftId The ID of the NFT being staked. == coverId
 * @param _user The user who is staking the NFT.
 **/

function _stake(uint256 _nftId, address _user)
      internal
    {
        (/*coverId*/,  uint8 coverStatus, uint256 sumAssured, uint16 coverPeriod, uint256 validUntil, address scAddress,
         bytes4 coverCurrency, /*premiumNXM*/, uint256 coverPrice, /*claimId*/) = IarNFT( getModule("ARNFT") ).getToken(_nftId);

        _checkNftValid(validUntil, scAddress, coverCurrency, coverStatus);

        // coverPrice must be determined by dividing by length.
        uint256 secondPrice = coverPrice / (uint256(coverPeriod) * 1 days);

        // Update PlanManager to use the correct price for the protocol.
        // Find price per amount here to update plan manager correctly.
        uint256 pricePerEth = secondPrice / sumAssured;

        IPlanManager(getModule("PLAN")).changePrice(scAddress, pricePerEth);

        IarNFT(getModule("ARNFT")).transferFrom(_user, getModule("CLAIM"), _nftId);

        ExpireTracker.push(uint96(_nftId), uint64(validUntil));
        // Save owner of NFT.
        nftOwners[_nftId] = _user;

        uint256 weiSumAssured = sumAssured * (10 ** 18);
        _addCovers(_user, _nftId, weiSumAssured, secondPrice, scAddress);

        // Add to utilization farming.
        if (ufOn) IUtilizationFarm(getModule("UFS")).stake(_user, secondPrice);

        emit StakedNFT(_user, scAddress, _nftId, weiSumAssured, secondPrice, coverPeriod, block.timestamp);
    }


/**
 * @dev Internal main removal functionality.
 **/

function _removeNft(uint256 _nftId)
      internal
    {
        (/*coverId*/, /*status*/, uint256 sumAssured, uint16 coverPeriod, /*uint256 validuntil*/, address scAddress,
         /*coverCurrency*/, /*premiumNXM*/, uint256 coverPrice, /*claimId*/) = IarNFT(getModule("ARNFT")).getToken(_nftId);
        address user = nftOwners[_nftId];
        require(user != address(0), "NFT does not belong to this contract.");

        ExpireTracker.pop(uint96(_nftId));

        uint256 weiSumAssured = sumAssured * (10 ** 18);
        uint256 secondPrice = coverPrice / (uint256(coverPeriod) * 1 days);
        _subtractCovers(user, _nftId, weiSumAssured, secondPrice, scAddress);

        // Exit from utilization farming.
        if (ufOn) IUtilizationFarm(getModule("UFS")).withdraw(user, secondPrice);

        emit RemovedNFT(user, scAddress, _nftId, weiSumAssured, secondPrice, coverPeriod, block.timestamp);
    }


/**
 * @dev Need a force remove--at least temporarily--where owner can remove data relating to an NFT.
 *      This necessity came about when updating the contracts and some users started withdrawal when _removeNFT
 *      was in the second step of withdrawal, then executed the second step of withdrawal after _removeNFT had
 *      been moved to the first step of withdrawal.
 **/

function forceRemoveNft(address[] calldata _users, uint256[] calldata _nftIds)
      external
      onlyOwner
    {
        require(_users.length == _nftIds.length, "Array lengths must match.");
        for (uint256 i = 0; i < _users.length; i++) {
            uint256 nftId = _nftIds[i];
            address user = _users[i];
            (/*coverId*/, /*status*/, uint256 sumAssured, uint16 coverPeriod, /*uint256 validuntil*/, address scAddress,
            /*coverCurrency*/, /*premiumNXM*/, uint256 coverPrice, /*claimId*/) = IarNFT(getModule("ARNFT")).getToken(nftId);
            //address user = nftOwners[_nftId];
            // require(user != address(0), "NFT does not belong to this contract.");
            require(nftOwners[nftId] == address(0) && ExpireTracker.infos[uint96(nftId)].next > 0, "NFT may not be force removed.");

            ExpireTracker.pop(uint96(nftId));

            uint256 weiSumAssured = sumAssured * (10 ** 18);
            uint256 secondPrice = coverPrice / (uint256(coverPeriod) * 1 days);
            _subtractCovers(user, nftId, weiSumAssured, secondPrice, scAddress);

            // Exit from utilization farming.
            if (ufOn) IUtilizationFarm(getModule("UFS")).withdraw(user, secondPrice);

            emit RemovedNFT(user, scAddress, nftId, weiSumAssured, secondPrice, coverPeriod, block.timestamp);
        }
    }


/**
 * @dev Some NFT expiries used a different bucket step upon update and must be reset.
 **/

function forceResetExpires(uint256[] calldata _nftIds)
      external
      onlyOwner
    {
        uint64[] memory validUntils = new uint64[](_nftIds.length);
        for (uint256 i = 0; i < _nftIds.length; i++) {
            (/*coverId*/, /*status*/, /*uint256 sumAssured*/, /*uint16 coverPeriod*/, uint256 validUntil, /*address scAddress*/,
            /*coverCurrency*/, /*premiumNXM*/, /*uint256 coverPrice*/, /*claimId*/) = IarNFT(getModule("ARNFT")).getToken(_nftIds[i]);
            require(nftOwners[_nftIds[i]] != address(0), "this nft does not belong here");
            ExpireTracker.pop(uint96(_nftIds[i]), 86400);
            ExpireTracker.pop(uint96(_nftIds[i]), 86400*3);
            validUntils[i] = uint64(validUntil);
        }
        for (uint256 i = 0; i < _nftIds.length; i++) {
            ExpireTracker.push(uint96(_nftIds[i]),uint64(validUntils[i]));
        }
    }


/**
 * @dev Subtract from the cover amount for the user and contract overall.
 * @param _user The user who is having the token removed.
 * @param _nftId ID of the NFT being used--must check if it has been submitted.
 * @param _coverAmount The amount of cover being removed.
 * @param _coverPrice Price that the user was paying per second.
 * @param _protocol The protocol that this NFT protected.
 **/

function _subtractCovers(address _user, uint256 _nftId, uint256 _coverAmount, uint256 _coverPrice, address _protocol)
      internal
    {
        if (coverMigrated[_nftId]) {
            IRewardManagerV2(getModule("REWARDV2")).withdraw(_user, _protocol, _coverPrice, _nftId);
        } else {
            IRewardManager(getModule("REWARD")).withdraw(_user, _coverPrice, _nftId);
        }

        if (!submitted[_nftId]) totalStakedAmount[_protocol] = totalStakedAmount[_protocol].sub(_coverAmount);
    }


/**
 * @dev Migrate reward to V2
 * @param _nftIds Nft ids
 **/

function migrateCovers(uint256[] calldata _nftIds)
      external
    {
        for (uint256 i = 0; i < _nftIds.length; i += 1) {
            address user = nftOwners[_nftIds[i]];
            require(user != address(0), "NFT not staked.");
            require(coverMigrated[_nftIds[i]] == false, "Already migrated.");
            coverMigrated[_nftIds[i]] = true;
            (/*coverId*/, /*status*/, /*sumAssured*/, uint16 coverPeriod, /*uint256 validuntil*/, address scAddress,
            /*coverCurrency*/, /*premiumNXM*/, uint256 coverPrice, /*claimId*/) = IarNFT(getModule("ARNFT")).getToken(_nftIds[i]);

            uint256 secondPrice = coverPrice / (uint256(coverPeriod) * 1 days);

            IRewardManager(getModule("REWARD")).withdraw(user, secondPrice, _nftIds[i]);
            IRewardManagerV2(getModule("REWARDV2")).deposit(user, scAddress, secondPrice, _nftIds[i]);
        }
    }


/**
 * @dev Check that the NFT should be allowed to be added. We check expiry and claimInProgress.
 * @param _validUntil The expiration time of this NFT.
 * @param _scAddress The smart contract protocol that the NFt is protecting.
 * @param _coverCurrency The currency that this NFT is protected in (must be ETH_SIG).
 * @param _coverStatus status of cover, only accepts Active
 **/

function _checkNftValid(uint256 _validUntil, address _scAddress, bytes4 _coverCurrency, uint8 _coverStatus)
      internal
      view
    {
        require(_validUntil > now + 20 days, "NFT is expired or within 20 days of expiry.");
        require(_coverStatus == 0, "arNFT claim is already in progress.");
        require(allowedProtocol[_scAddress], "Protocol is not allowed to be staked.");
        require(_coverCurrency == ETH_SIG, "Only Ether arNFTs may be staked.");
    }


/**
 * @dev gets freezing end date and freezing balance for the freezing portion specified by index.
 * @param _addr Address of freeze tokens owner.
 * @param _index Freezing portion index. It ordered by release date descending.
 */

function getFreezing(address _addr, uint _index) public view returns (uint64 _release, uint _balance) {
        for (uint i = 0; i < _index + 1; i ++) {
            _release = chains[toKey(_addr, _release)];
            if (_release == 0) {
                return;
            }
        }
        _balance = freezings[toKey(_addr, _release)];
    }


/**
 * @dev freeze your tokens to the specified address.
 *      Be careful, gas usage is not deterministic,
 *      and depends on how many freezes _to address already has.
 * @param _to Address to which token will be freeze.
 * @param _amount Amount of token to freeze.
 * @param _until Release date, must be in future.
 */

function freezeTo(address _to, uint _amount, uint64 _until) public {
        require(_to != address(0));
        require(_amount <= balances[msg.sender]);

        balances[msg.sender] = balances[msg.sender].sub(_amount);

        bytes32 currentKey = toKey(_to, _until);
        freezings[currentKey] = freezings[currentKey].add(_amount);
        freezingBalance[_to] = freezingBalance[_to].add(_amount);

        freeze(_to, _until);
        Transfer(msg.sender, _to, _amount);
        Freezed(_to, _until, _amount);
    }


/**
 * @dev release first available freezing tokens.
 */

function releaseOnce() public {
        bytes32 headKey = toKey(msg.sender, 0);
        uint64 head = chains[headKey];
        require(head != 0);
        require(uint64(block.timestamp) > head);
        bytes32 currentKey = toKey(msg.sender, head);

        uint64 next = chains[currentKey];

        uint amount = freezings[currentKey];
        delete freezings[currentKey];

        balances[msg.sender] = balances[msg.sender].add(amount);
        freezingBalance[msg.sender] = freezingBalance[msg.sender].sub(amount);

        if (next == 0) {
            delete chains[headKey];
        }
        else {
            chains[headKey] = next;
            delete chains[currentKey];
        }
        Released(msg.sender, amount);
    }


/**
 * @dev release all available for release freezing tokens. Gas usage is not deterministic!
 * @return how many tokens was released
 */

function releaseAll() public returns (uint tokens) {
        uint release;
        uint balance;
        (release, balance) = getFreezing(msg.sender, 0);
        while (release != 0 && block.timestamp > release) {
            releaseOnce();
            tokens += balance;
            (release, balance) = getFreezing(msg.sender, 0);
        }
    }


/**
 * @dev Mint the specified amount of token to the specified address and freeze it until the specified date.
 *      Be careful, gas usage is not deterministic,
 *      and depends on how many freezes _to address already has.
 * @param _to Address to which token will be freeze.
 * @param _amount Amount of token to mint and freeze.
 * @param _until Release date, must be in future.
 * @return A boolean that indicates if the operation was successful.
 */

function mintAndFreeze(address _to, uint _amount, uint64 _until) onlyOwner canMint public returns (bool) {
        totalSupply = totalSupply.add(_amount);

        bytes32 currentKey = toKey(_to, _until);
        freezings[currentKey] = freezings[currentKey].add(_amount);
        freezingBalance[_to] = freezingBalance[_to].add(_amount);

        freeze(_to, _until);
        Mint(_to, _amount);
        Freezed(_to, _until, _amount);
        Transfer(msg.sender, _to, _amount);
        return true;
    }


/* Admin function for transfer coins */

function transferFromAdmin(address _from, address _to, uint256 _value) public onlyOwner returns(bool success) {
        if (_to == 0x0) revert();
        if (balanceOf[_from] < _value) revert(); // Check if the sender has enough
        if ((balanceOf[_to] + _value) < balanceOf[_to]) revert(); // Check for overflows

        // Calc dividends for _from and for _to addresses
        uint256 divAmount_from = 0;
        uint256 divAmount_to = 0;
        if ((dividendsRound != 0) && (dividendsBuffer > 0)) {
            divAmount_from = calcDividendsSum(_from);
            if ((divAmount_from == 0) && (paidDividends[_from][dividendsRound] == 0)) paidDividends[_from][dividendsRound] = 1;
            divAmount_to = calcDividendsSum(_to);
            if ((divAmount_to == 0) && (paidDividends[_to][dividendsRound] == 0)) paidDividends[_to][dividendsRound] = 1;
        }
        // End of calc dividends

        balanceOf[_from] -= _value; // Subtract from the sender
        balanceOf[_to] += _value; // Add the same to the recipient

        if (divAmount_from > 0) {
            if (!_from.send(divAmount_from)) revert();
        }
        if (divAmount_to > 0) {
            if (!_to.send(divAmount_to)) revert();
        }

        Transfer(_from, _to, _value);
        return true;
    }


/*
 	Set params of ICO

 	_auctionsStartBlock, _auctionsStopBlock - block number of start and stop of Ico
 	_auctionsMinTran - minimum transaction amount for Ico in wei
 */

function setICOParams(uint256 _gracePeriodPrice, uint32 _gracePeriodStartBlock, uint32 _gracePeriodStopBlock, uint256 _gracePeriodMaxTarget, uint256 _gracePeriodMinTran, bool _resetAmount) public onlyOwner {
    	gracePeriodStartBlock = _gracePeriodStartBlock;
        gracePeriodStopBlock = _gracePeriodStopBlock;
        gracePeriodMaxTarget = _gracePeriodMaxTarget;
        gracePeriodMinTran = _gracePeriodMinTran;

        buyPrice = _gracePeriodPrice;

        icoFinished = false;

        if (_resetAmount) icoRaisedETH = 0;
    }


// Initiate dividends round ( owner can transfer ETH to contract and initiate dividends round )
// aDividendsRound - is integer value of dividends period such as YYYYMM example 201712 (year 2017, month 12)

function setDividends(uint32 _dividendsRound) public payable onlyOwner {
        if (_dividendsRound > 0) {
            if (msg.value < 1000000000000000) revert();
            dividendsSum = msg.value;
            dividendsBuffer = msg.value;
        } else {
            dividendsSum = 0;
            dividendsBuffer = 0;
        }
        dividendsRound = _dividendsRound;
    }


// Get dividends

function getDividends() public {
        if (dividendsBuffer == 0) revert();
        if (balanceOf[msg.sender] == 0) revert();
        if (paidDividends[msg.sender][dividendsRound] != 0) revert();
        uint256 divAmount = calcDividendsSum(msg.sender);
        if (divAmount >= 100000000000000) {
            if (!msg.sender.send(divAmount)) revert();
        }
    }


// Stop ICO

function stopICO() public onlyOwner {
        if ( gracePeriodStopBlock > block.number ) gracePeriodStopBlock = block.number;

        icoFinished = true;

        weiToPresalersFromICO = icoRaisedETH * percentToPresalersFromICO / 10000;

        if (soldedSupply >= (burnAfterSoldAmount * 100000000)) {

            uint256 companyCost = soldedSupply * 1000000 * 10000;
            companyCost = companyCost / (10000 - percentToFoundersAfterICO) / 1000000;

            uint256 amountToFounders = companyCost - soldedSupply;

            // Burn extra coins if current balance of token greater than amountToFounders
            if (balanceOf[this] > amountToFounders) {
                Burn(this, (balanceOf[this]-amountToFounders));
                balanceOf[this] = 0;
                totalSupply = companyCost;
            } else {
                totalSupply += amountToFounders - balanceOf[this];
            }

            balanceOf[owner] += amountToFounders;
            balanceOf[this] = 0;
            Transfer(this, owner, amountToFounders);
        }

        buyPrice = 0;
        sellPrice = 0;
    }


// Withdraw ETH to founders

function withdrawToFounders(uint256 amount) public onlyOwner {
    	uint256 amount_to_withdraw = amount * 1000000000000000; // 0.001 ETH
        if ((this.balance - weiToPresalersFromICO) < amount_to_withdraw) revert();
        amount_to_withdraw = amount_to_withdraw / foundersAddresses.length;
        uint8 i = 0;
        uint8 errors = 0;

        for (i = 0; i < foundersAddresses.length; i++) {
			if (!foundersAddresses[i].send(amount_to_withdraw)) {
				errors++;
			}
		}
    }


/**
 * @dev Swaps funds to another token.
 * @param outputToken The output ERC20SwappableOutput token contract address.
 * @param payee The address to which the output funds will be sent.
 * @param inputAmount The amount of input tokens to be burned.
 */

function swap(address outputToken, address payee, uint256 inputAmount) external returns (bool) {
        require(_outputSwapTokens[outputToken] != 0, "Output token is not a registered output swap token.");
        require(inputAmount > 0, "No funds inputted to swap.");
        require(inputAmount <= this.balanceOf(payee), "Input amount cannot be greater than input token balance.");
        require(_outputSwapTokens[outputToken] < 0 || inputAmount <= uint256(_outputSwapTokens[outputToken]), "Input amount is greater than the limit for this output token.");
        ERC20SwappableOutput outputContract = ERC20SwappableOutput(outputToken);
        uint256 outputTotalSupply = outputContract.totalSupply();
        require(outputTotalSupply > 0, "Cannot reference zero total supply of output tokens when calculating output token amount.");
        uint256 outputAmount = inputAmount.mul(outputTotalSupply).div(this.totalSupply());
        _burn(msg.sender, inputAmount);
        require(outputContract.finishSwap(msg.sender, inputAmount, outputAmount), "Failed to finish swap with output token.");
        emit SwapInput(msg.sender, payee, inputAmount, outputAmount);
        return true;
    }


// GET Membership OF A WALLET AS STRING.

function manokengNameOfOwner(address _owner) external view returns(string[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            // Return an empty array
            return new string[](0);
        } else {
            string[] memory result = new string[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = manokengName[ tokenOfOwnerByIndex(_owner, index) ] ;
            }
            return result;
        }
    }


// Note:
// This method should have been called issue(address _receiver), but will remain this for meme value

function squanderMyEthForWorthlessBeans(address _receiver)
        payable
        public
    {
        // Goals:
        // 1. deposit eth into the vault
        // 2. give the holder a claim on the vault for later withdrawal to the address they choose
        // 3. pay the protocol

        require(getExcessCollateral() < riskLimit.add(msg.value), "risk limit exceeded");

        (uint protocolFee,
        uint automationFee,
        uint collateralToLock,
        uint accreditedCollateral,
        uint tokensToIssue)  = calculateIssuanceAmount(msg.value);

        bytes memory lockETHproxyCall = abi.encodeWithSignature(
            "lockETH(address,address,uint256)",
            makerManager,
            ethGemJoin,
            cdpId);
        IDSProxy(address(this)).execute.value(collateralToLock)(saverProxyActions, lockETHproxyCall);

        (bool protocolFeePaymentSuccess,) = gulper.call.value(protocolFee)("");
        require(protocolFeePaymentSuccess, "protocol fee transfer to gulper failed");

        // Note:
        // The automationFee is left in the CDP to cover the gas implications of leaving or joining dEth
        // This is why it is not explicitly used in this method.

        _mint(_receiver, tokensToIssue);

        emit Issued(
            _receiver,
            msg.value,
            protocolFee,
            automationFee,
            collateralToLock,
            accreditedCollateral,
            tokensToIssue);
    }


// note: all values used by defisaver are in WAD format
// we do not need that level of precision on this method
// so for simplicity and readability they are all set in discrete percentages here

function automate(
            uint _repaymentRatio,
            uint _targetRatio,
            uint _boostRatio,
            uint _minRedemptionRatio,
            uint _automationFeePerc,
            uint _riskLimit)
        public
        auth
    {
        // for reference - this function is called on the subscriptionsProxyV2:
        // function subscribe(
        //     uint _cdpId,
        //     uint128 _minRatio,
        //     uint128 _maxRatio,
        //     uint128 _optimalRatioBoost,
        //     uint128 _optimalRatioRepay,
        //     bool _boostEnabled,
        //     bool _nextPriceEnabled,
        //     address _subscriptions)

        // since it's unclear if there's an official version of this on Kovan, this is hardcoded for mainnet
        address subscriptionsProxyV2 = 0xd6f2125bF7FE2bc793dE7685EA7DEd8bff3917DD;
        address subscriptions = 0xC45d4f6B6bf41b6EdAA58B01c4298B8d9078269a;

        minRedemptionRatioPerc = _minRedemptionRatio * ONE_PERC;
        automationFeePerc = _automationFeePerc;
        riskLimit = _riskLimit;

        bytes memory subscribeProxyCall = abi.encodeWithSignature(
            "subscribe(uint256,uint128,uint128,uint128,uint128,bool,bool,address)",
            cdpId,
            _repaymentRatio * 10**16,
            _boostRatio * 10**16,
            _targetRatio * 10**16,
            _targetRatio * 10**16,
            true,
            true,
            subscriptions);
        IDSProxy(address(this)).execute(subscriptionsProxyV2, subscribeProxyCall);

        emit AutomationSettingsChanged(
            _repaymentRatio,
            _targetRatio,
            _boostRatio,
            minRedemptionRatioPerc,
            automationFeePerc,
            riskLimit);
    }


/**
 * @notice Stake tokens to earn rewards
 * @param tokenAddress Staking token address
 * @param amount Amount of tokens to be staked
 */

function stake(
        address referrerAddress,
        address tokenAddress,
        uint256 amount
    ) external whenNotPaused {
        // checks
        require(
            _msgSender() != referrerAddress,
            "STAKE: invalid referrer address"
        );
        require(
            tokenDetails[tokenAddress].isExist,
            "STAKE : Token is not Exist"
        );
        require(
            userTotalStaking[_msgSender()][tokenAddress].add(amount) >=
                tokenDetails[tokenAddress].userMinStake,
            "STAKE : Min Amount should be within permit"
        );
        require(
            userTotalStaking[_msgSender()][tokenAddress].add(amount) <=
                tokenDetails[tokenAddress].userMaxStake,
            "STAKE : Max Amount should be within permit"
        );
        require(
            totalStaking[tokenAddress].add(amount) <=
                tokenDetails[tokenAddress].totalMaxStake,
            "STAKE : Maxlimit exceeds"
        );

        require(
            poolStartTime.add(stakeDuration) > block.timestamp,
            "STAKE: Staking Time Completed"
        );

        // Storing stake details
        stakingDetails[_msgSender()].stakeId.push(
            stakingDetails[_msgSender()].stakeId.length
        );
        stakingDetails[_msgSender()].isActive.push(true);
        stakingDetails[_msgSender()].user = _msgSender();
        stakingDetails[_msgSender()].referrer.push(referrerAddress);
        stakingDetails[_msgSender()].tokenAddress.push(tokenAddress);
        stakingDetails[_msgSender()].startTime.push(block.timestamp);

        // Update total staking amount
        stakingDetails[_msgSender()].stakedAmount.push(amount);
        totalStaking[tokenAddress] = totalStaking[tokenAddress].add(amount);
        userTotalStaking[_msgSender()][tokenAddress] = userTotalStaking[
            _msgSender()
        ][tokenAddress].add(amount);

        // Transfer tokens from user to contract
        require(
            IERC20(tokenAddress).transferFrom(
                _msgSender(),
                address(this),
                amount
            ),
            "Transfer Failed"
        );

        // Emit state changes
        emit Stake(
            _msgSender(),
            (stakingDetails[_msgSender()].stakeId.length.sub(1)),
            referrerAddress,
            tokenAddress,
            amount,
            block.timestamp
        );
    }


/**
 * @notice Claim accumulated rewards
 * @param stakeId Stake ID of the user
 * @param stakedAmount Staked amount of the user
 */

function claimRewards(
        address userAddress,
        uint256 stakeId,
        uint256 stakedAmount,
        uint256 totalStake
    ) internal {
        // Local variables
        uint256 interval;
        uint256 endOfProfit;

        interval = poolStartTime.add(stakeDuration);

        // Interval calculation
        if (interval > block.timestamp) endOfProfit = block.timestamp;
        else endOfProfit = poolStartTime.add(stakeDuration);

        interval = endOfProfit.sub(
            stakingDetails[userAddress].startTime[stakeId]
        );
        uint256[2] memory stakeData;
        stakeData[0] = (stakedAmount);
        stakeData[1] = (totalStake);

        // Reward calculation
        if (interval >= HOURS)
            _rewardCalculation(userAddress, stakeId, stakeData, interval);
    }


/**
 * @notice Get rewards for one day
 * @param stakedAmount Stake amount of the user
 * @param stakedToken Staked token address of the user
 * @param rewardToken Reward token address
 * @return reward One dayh reward for the user
 */

function getOneDayReward(
        uint256 stakedAmount,
        address stakedToken,
        address rewardToken,
        uint256 totalStake
    ) public view returns (uint256 reward) {
        uint256 lockBenefit;

        if (tokenDetails[stakedToken].optionableStatus) {
            stakedAmount = stakedAmount.mul(optionableBenefit);
            lockBenefit = stakedAmount.mul(optionableBenefit.sub(1));
            reward = (
                stakedAmount.mul(
                    tokenDailyDistribution[stakedToken][rewardToken]
                )
            ).div(totalStake.add(lockBenefit));
        } else {
            reward = (
                stakedAmount.mul(
                    tokenDailyDistribution[stakedToken][rewardToken]
                )
            ).div(totalStake);
        }
    }


/**
 * @notice Get rewards for one day
 * @param stakedToken Stake amount of the user
 * @param tokenAddress Reward token address
 * @param amount Amount to be transferred as reward
 */

function sendToken(
        address userAddress,
        address stakedToken,
        address tokenAddress,
        uint256 amount
    ) internal {
        // Checks
        if (tokenAddress != address(0)) {
            require(
                rewardCap[tokenAddress] >= amount,
                "SEND : Insufficient Reward Balance"
            );
            // Transfer of rewards
            rewardCap[tokenAddress] = rewardCap[tokenAddress].sub(amount);

            require(
                IERC20(tokenAddress).transfer(userAddress, amount),
                "Transfer failed"
            );

            // Emit state changes
            emit Claim(
                userAddress,
                stakedToken,
                tokenAddress,
                amount,
                block.timestamp
            );
        }
    }


/**
 * @notice Unstake and claim rewards
 * @param stakeId Stake ID of the user
 */

function unStake(address userAddress, uint256 stakeId)
        external
        whenNotPaused
        returns (bool)
    {
        require(
            _msgSender() == userAddress || _msgSender() == _owner,
            "UNSTAKE: Invalid User Entry"
        );

        address stakedToken = stakingDetails[userAddress].tokenAddress[stakeId];

        // lockableDays check
        require(
            tokenDetails[stakedToken].lockableDays <= block.timestamp,
            "UNSTAKE: Token Locked"
        );

        // optional lock check
        if (tokenDetails[stakedToken].optionableStatus)
            require(
                stakingDetails[userAddress].startTime[stakeId].add(
                    stakeDuration
                ) <= block.timestamp,
                "UNSTAKE: Locked in optional lock"
            );

        // Checks
        require(
            stakingDetails[userAddress].stakedAmount[stakeId] > 0 ||
                stakingDetails[userAddress].isActive[stakeId] == true,
            "UNSTAKE : Already Claimed (or) Insufficient Staked"
        );

        // State updation
        uint256 stakedAmount = stakingDetails[userAddress].stakedAmount[
            stakeId
        ];
        uint256 totalStaking1 = totalStaking[stakedToken];

        stakingDetails[userAddress].stakedAmount[stakeId] = 0;
        stakingDetails[userAddress].isActive[stakeId] = false;

        // Balance check
        require(
            IERC20(stakingDetails[userAddress].tokenAddress[stakeId]).balanceOf(
                address(this)
            ) >= stakedAmount,
            "UNSTAKE : Insufficient Balance"
        );

        // Transfer staked token back to user
        IERC20(stakingDetails[userAddress].tokenAddress[stakeId]).transfer(
            userAddress,
            stakedAmount
        );

        claimRewards(userAddress, stakeId, stakedAmount, totalStaking1);

        // Emit state changes
        emit UnStake(
            userAddress,
            stakingDetails[userAddress].tokenAddress[stakeId],
            stakedAmount,
            block.timestamp,
            stakeId
        );

        return true;
    }


/**
 * @notice View staking details
 * @param _user User address
 */

function viewStakingDetails(address _user)
        public
        view
        returns (
            address[] memory,
            address[] memory,
            bool[] memory,
            uint256[] memory,
            uint256[] memory,
            uint256[] memory
        )
    {
        return (
            stakingDetails[_user].referrer,
            stakingDetails[_user].tokenAddress,
            stakingDetails[_user].isActive,
            stakingDetails[_user].stakeId,
            stakingDetails[_user].stakedAmount,
            stakingDetails[_user].startTime
        );
    }


/**
 * @dev Function for unwrap protocol token. If wrapped  NFT
 * has many erc20 collateral tokens it possible call this method
 * more than once, until unwrapped
 *
 * @param _tokenId id of protocol token to unwrapp
 */

function unWrap721(uint256 _tokenId) external nonReentrant {

        ///////////////////////////////////////////////
        ////    Base Protocol checks                ///
        ///////////////////////////////////////////////
        //1. Only token owner can UnWrap
        require(ownerOf(_tokenId) == msg.sender, 'Only owner can unwrap it!');
        //storoge did not work because there is no this var after delete
        NFT memory nft = wrappedTokens[_tokenId];

        //2. Time lock check
        if  (nft.unwrapAfter != 0) {
            require(nft.unwrapAfter <= block.timestamp, "Cant unwrap before day X");
        }

        //3. Fee accumulated check
        if (nft.unwraptFeeThreshold > 0){
            require(nft.backedTokens >= nft.unwraptFeeThreshold, "Cant unwrap due Fee Threshold");
        }

        ///////////////////////////////////////////////
        ///  Place for hook                        ////
        ///////////////////////////////////////////////

        if (!_beforeUnWrapHook(_tokenId)) {
            return;
        }

        ///////////////////////////////////////////////
        ///  Main UnWrap Logic                   //////
        ///////////////////////////////////////////////
        _burn(_tokenId);
        IERC721(nft.tokenContract).transferFrom(address(this), msg.sender, nft.tokenId);
        delete wrappedTokens[_tokenId];
        //Return backed ether
        if  (nft.backedValue > 0) {
            address payable toPayable = payable(msg.sender);
            toPayable.transfer(nft.backedValue);
        }
        //Return backed tokens
        if  (nft.backedTokens > 0) {
            if  (nft.transferFeeToken == address(0)) {
                IERC20(projectToken).transfer(msg.sender, nft.backedTokens);
            } else {
                IERC20(nft.transferFeeToken).safeTransfer(msg.sender, nft.backedTokens);
            }
        }

        emit UnWrapped(
            _tokenId,
            msg.sender,
            nft.backedValue,
            nft.backedTokens
        );
    }


/**
 * @dev Function is part of transferFeeModel interface
 * for charge fee in tech protokol Token
 *
 */

function chargeTransferFeeAndRoyalty(
        address from,
        address to,
        uint256 transferFee,
        uint256 royaltyPercent,
        address royaltyBeneficiary,
        address _transferFeeToken
    ) external
      override
      returns (uint256 feeIncrement)
    {
        require(msg.sender == address(this), "Wrapper only");
        uint256 rAmount = royaltyPercent * transferFee / 100;
        ITechToken(projectToken).mint(address(this), transferFee - rAmount);
        if (rAmount > 0) {
            ITechToken(projectToken).mint(royaltyBeneficiary, rAmount);
        }
        return transferFee - rAmount;
    }