/**
 *  @notice debt ratio in same terms for reserve or liquidity bonds
 *  @return uint
 */

function standardizedDebtRatio() external view returns ( uint ) {
        if ( isLiquidityBond ) {
            return debtRatio().mul( IBondCalculator( bondCalculator ).markdown( principle ) ).div( 1e9 );
        } else {
            return debtRatio();
        }
    }


/**
 *  @notice amount to decay total debt by
 *  @return decay_ uint
 */

function debtDecay() public view returns ( uint decay_ ) {
        uint blocksSinceLast = block.number.sub( lastDecay );
        decay_ = totalDebt.mul( blocksSinceLast ).div( terms.vestingTerm );
        if ( decay_ > totalDebt ) {
            decay_ = totalDebt;
        }
    }


/**
 *  @notice calculate how far into vesting a depositor is
 *  @param _depositor address
 *  @return percentVested_ uint
 */

function percentVestedFor( address _depositor ) public view returns ( uint percentVested_ ) {
        Bond memory bond = bondInfo[ _depositor ];
        uint blocksSinceLast = block.number.sub( bond.lastBlock );
        uint vesting = bond.vesting;

        if ( vesting > 0 ) {
            percentVested_ = blocksSinceLast.mul( 10000 ).div( vesting );
        } else {
            percentVested_ = 0;
        }
    }


/**
 *  @notice calculate amount of OHM available for claim by depositor
 *  @param _depositor address
 *  @return pendingPayout_ uint
 */

function pendingPayoutFor( address _depositor ) external view returns ( uint pendingPayout_ ) {
        uint percentVested = percentVestedFor( _depositor );
        uint payout = bondInfo[ _depositor ].payout;

        if ( percentVested >= 10000 ) {
            pendingPayout_ = payout;
        } else {
            pendingPayout_ = payout.mul( percentVested ).div( 10000 );
        }
    }


/**
 *  @notice allow anyone to send lost tokens (excluding principle or OHM) to the DAO
 *  @return bool
 */

function recoverLostToken( address _token ) external returns ( bool ) {
        require( _token != OHM );
        require( _token != principle );
        IERC20( _token ).safeTransfer( DAO, IERC20( _token ).balanceOf( address(this) ) );
        return true;
    }


/**
 	 * Anyone can call the function to deploy a new payment handler.
 	 * The new contract will be created, added to the list, and an event fired.
 	 */

function deployNewHandler() public {
		// Deploy the new contract
		PaymentHandler createdHandler = new PaymentHandler(this);

		// Add it to the list and the mapping
		handlerList.push(address(createdHandler));
		handlerMap[address(createdHandler)] = true;

		// Emit event to let watchers know that a new handler was created
		emit HandlerCreated(address(createdHandler));
	}


/**
 	 * This function is called by handlers when they receive ETH payments.
 	 */

function firePaymentReceivedEvent(address to, address from, uint256 amount) public {
		// Verify the call is coming from a handler
		require(handlerMap[msg.sender], "Only payment handlers are allowed to trigger payment events.");

		// Emit the event
		emit EthPaymentReceived(to, from, amount);
	}


/**
 	 * Allows a caller to sweep multiple handlers in one transaction
 	 */

function multiHandlerSweep(address[] memory handlers, IERC20 tokenContract) public {
		for (uint i = 0; i < handlers.length; i++) {

			// Whitelist calls to only handlers
			require(handlerMap[handlers[i]], "Only payment handlers are valid sweep targets.");

			// Trigger sweep
			PaymentHandler(address(uint160(handlers[i]))).sweepTokens(tokenContract);
		}
	}


/* Transfers based on an offline signed transfer instruction. */

function delegatedTransfer(address from, address to, uint amount, string narrative,
                                     uint maxExecutorFeeInToken, /* client provided max fee for executing the tx */
                                     bytes32 nonce, /* random nonce generated by client */
                                     /* ^^^^ end of signed data ^^^^ */
                                     bytes signature,
                                     uint requestedExecutorFeeInToken /* the executor can decide to request lower fee */
                                     )
    external {
        bytes32 txHash = keccak256(abi.encodePacked(this, from, to, amount, narrative, maxExecutorFeeInToken, nonce));

        _checkHashAndTransferExecutorFee(txHash, signature, from, maxExecutorFeeInToken, requestedExecutorFeeInToken);

        _transfer(from, to, amount, narrative);
    }


/* transferAndNotify based on an  instruction signed offline  */

function delegatedTransferAndNotify(address from, TokenReceiver target, uint amount, uint data,
                                     uint maxExecutorFeeInToken, /* client provided max fee for executing the tx */
                                     bytes32 nonce, /* random nonce generated by client */
                                     /* ^^^^ end of signed data ^^^^ */
                                     bytes signature,
                                     uint requestedExecutorFeeInToken /* the executor can decide to request lower fee */
                                     )
    external {
        bytes32 txHash = keccak256(abi.encodePacked(this, from, target, amount, data, maxExecutorFeeInToken, nonce));

        _checkHashAndTransferExecutorFee(txHash, signature, from, maxExecutorFeeInToken, requestedExecutorFeeInToken);

        _transfer(from, target, amount, "");
        target.transferNotification(from, amount, data);
    }


/**
 * @dev    Selling tokens back to the bonding curve for collateral.
 * @param  _numTokens: The number of tokens that you want to burn.
 */

function burn(uint256 _numTokens) external onlyActive() returns(bool) {
        require(
            balances[msg.sender] >= _numTokens,
            "Not enough tokens available"
        );

        uint256 reward = rewardForBurn(_numTokens);

        totalSupply_ = totalSupply_.sub(_numTokens);
        balances[msg.sender] = balances[msg.sender].sub(_numTokens);

        require(
            collateralToken_.transfer(
                msg.sender,
                reward
            ),
            "Tokens not sent"
        );

        emit Transfer(msg.sender, address(0), _numTokens);
        emit Burn(msg.sender, _numTokens, reward);
        return true;
    }


/**
 * @notice This function returns the amount of tokens one can receive for a
 *         specified amount of collateral token.
 * @param  _collateralTokenOffered : Amount of reserve token offered for
 *         purchase.
 * @return uint256 : The amount of tokens once can purchase with the
 *         specified collateral.
 */

function collateralToTokenBuying(
        uint256 _collateralTokenOffered
    )
        external
        view
        returns(uint256)
    {
        // Works out the amount of collateral for fee
        uint256 fee = _collateralTokenOffered.mul(feeRate_).div(100);
        // Removes the fee amount from the collateral offered
        uint256 amountLessFee = _collateralTokenOffered.sub(fee);
        // Works out the inverse curve of the pool with the fee removed amount
        return _inverseCurveIntegral(
                _curveIntegral(totalSupply_).add(amountLessFee)
            ).sub(totalSupply_);
    }


/**
 * @dev    Allows token holders to withdraw collateral in return for tokens
 * 		after the market has been finalised.
 * @param 	_amount: The amount of tokens they want to withdraw
 */

function withdraw(uint256 _amount) public returns(bool) {
        // Ensures withdraw can only be called in an inactive market
        require(!active_, "Market not finalised");
        // Ensures the sender has enough tokens
        require(_amount <= balances[msg.sender], "Insufficient funds");
        // Ensures there are no anomaly withdraws that might break calculations
        require(_amount > 0, "Cannot withdraw 0");

        // Removes amount from user balance
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        // Gets the balance of the market (vault may send excess funding)
        uint256 balance = collateralToken_.balanceOf(address(this));

        // Performs a flat linear 100% collateralized sale
        uint256 collateralToTransfer = balance.mul(_amount).div(totalSupply_);
        // Removes token amount from the total supply
        totalSupply_ = totalSupply_.sub(_amount);

        // Ensures the sender is sent their collateral amount
        require(
            collateralToken_.transfer(msg.sender, collateralToTransfer),
            "Dai transfer failed"
        );

        emit Transfer(msg.sender, address(0), _amount);
        emit Burn(msg.sender, _amount, collateralToTransfer);

        return true;
    }


/**
 	  * @dev	Returns the required collateral amount for a volume of bonding
 	  *			curve tokens.
 * @notice The curve intergral code will reject any values that are too
 *         small or large, that could result in over/under flows.
 	  * @param	_numTokens: The number of tokens to calculate the price of
 * @return uint256 : The required collateral amount for a volume of bonding
 *         curve tokens.
 */

function priceToMint(uint256 _numTokens) public view returns(uint256) {
        // Gets the balance of the market
        uint256 balance = collateralToken_.balanceOf(address(this));
        // Performs the curve intergral with the relavant vaules
        uint256 collateral = _curveIntegral(
                totalSupply_.add(_numTokens)
            ).sub(balance);
        // Sets the base unit for decimal shift
        uint256 baseUnit = 100;
        // Adds the fee amount
        uint256 result = collateral.mul(100).div(baseUnit.sub(feeRate_));
        return result;
    }


/**
 	  * @dev	Returns the required collateral amount for a volume of bonding
 	  *			curve tokens
 	  * @param	_numTokens: The number of tokens to work out the collateral
 	  *			vaule of
 * @return uint256: The required collateral amount for a volume of bonding
 *         curve tokens
 */

function rewardForBurn(uint256 _numTokens) public view returns(uint256) {
        // Gets the curent balance of the market
        uint256 poolBalanceFetched = collateralToken_.balanceOf(address(this));
        // Returns the pool balance minus the curve intergral of the removed
        // tokens
        return poolBalanceFetched.sub(
            _curveIntegral(totalSupply_.sub(_numTokens))
        );
    }


/**
 * @notice Transfer ownership token from msg.sender to a specified address.
 * @param  _to : The address to transfer to.
 * @param  _value : The amount to be transferred.
 */

function transfer(address _to, uint256 _value) public returns (bool) {
        require(_value <= balances[msg.sender], "Insufficient funds");
        require(_to != address(0), "Target account invalid");

        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * @dev    Initialized the contract, sets up owners and gets the market
 *         address. This function exists because the Vault does not have
 *         an address until the constructor has finished running. The
 *         cumulative funding threshold is set here because of gas issues
 *         within the constructor.
 * @param _market: The market that will be sending this vault it's
 *         collateral.
 */

function initialize(
        address _market
    )
        external
        onlyWhitelistAdmin()
        returns(bool)
    {
        require(_market != address(0), "Contracts initialized");
        // Stores the market in storage
        market_ = IMarket(_market);
        // Removes the market factory contract as an admin
        super.renounceWhitelistAdmin();

        // Adding all previous rounds funding goals to the cumulative goal
        for(uint8 i = 0; i < totalRounds_; i++) {
            if(i == 0) {
                fundingPhases_[i].cumulativeFundingThreshold.add(
                    fundingPhases_[i].fundingThreshold
                );
            }
            fundingPhases_[i].cumulativeFundingThreshold.add(
                fundingPhases_[i-1].cumulativeFundingThreshold
            );
        }
        _active = true;

        return true;
    }


/**
 * @dev    This function sends the vaults funds to the market, and sets the
 *         outstanding withdraw to 0.
 * @notice If this function is called before the end of all phases, all
 *         unclaimed (outstanding) funding will be sent to the market to be
 *         redistributed.
 */

function terminateMarket()
        public
        isActive()
        onlyWhitelistAdmin()
    {
        uint256 remainingBalance = collateralToken_.balanceOf(address(this));
        // This ensures that if the creator has any outstanding funds, that
        // those funds do not get sent to the market.
        if(outstandingWithdraw_ > 0) {
            remainingBalance = remainingBalance.sub(outstandingWithdraw_);
        }
        // Transfers remaining balance to the market
        require(
            collateralToken_.transfer(address(market_), remainingBalance),
            "Transfering of funds failed"
        );
        // Finalizes market (stops buys/sells distributes collateral evenly)
        require(market_.finaliseMarket(), "Market termination error");
    }


/**
 * @notice This function allows for the creation of a new market,
 *         consisting of a curve and vault. If the creator address is the
 *         same as the deploying address the market the initialization of
 *         the market will fail.
 * @dev    Vyper cannot handle arrays of unknown length, and thus the
 *         funding goals and durations will only be stored in the vault,
 *         which is Solidity.
 * @param  _fundingGoals This is the amount wanting to be raised in each
 *         round, in collateral.
 * @param  _phaseDurations The time for each round in months. This number
 *         is covered into block time within the vault.
 * @param  _creator Address of the researcher.
 * @param  _curveType Curve selected.
 * @param  _feeRate The percentage of fee. e.g: 60
 */

function deployMarket(
        uint256[] calldata _fundingGoals,
        uint256[] calldata _phaseDurations,
        address _creator,
        uint256 _curveType,
        uint256 _feeRate
    )
        external
        onlyAnAdmin()
    {
        // Breaks down the return of the curve data
        (address curveLibrary,, bool curveState) = curveRegistry_.getCurveData(
            _curveType
        );

        require(_feeRate > 0, "Fee rate too low");
        require(_feeRate < 100, "Fee rate too high");
        require(_creator != address(0), "Creator address invalid");
        require(curveState, "Curve inactive");
        require(curveLibrary != address(0), "Curve library invalid");

        address newVault = address(new Vault(
            _fundingGoals,
            _phaseDurations,
            _creator,
            address(collateralToken_),
            address(moleculeVault_)
        ));

        address newMarket = address(new Market(
            _feeRate,
            newVault,
            curveLibrary,
            address(collateralToken_)
        ));

        require(Vault(newVault).initialize(newMarket), "Vault not initialized");
        marketRegistry_.registerMarket(newMarket, newVault, _creator);
    }


/**
 * @dev Execute single-hop swaps for swapExactIn trade type. Used for swaps
 * returned from viewSplit function and legacy off-chain SOR.
 *
 * @param swaps Array of single-hop swaps.
 * @param tokenIn Input token.
 * @param tokenOut Output token.
 * @param totalAmountIn Total amount of tokenIn.
 * @param minTotalAmountOut Minumum amount of tokenOut.
 * @param useUtilityToken Flag to determine if the protocol swap fee is paid using UtilityToken or TokenIn.
 */

function batchSwapExactIn(
        Swap[] memory swaps,
        IXToken tokenIn,
        IXToken tokenOut,
        uint256 totalAmountIn,
        uint256 minTotalAmountOut,
        bool useUtilityToken
    ) public returns (uint256 totalAmountOut) {
        transferFrom(tokenIn, totalAmountIn);

        for (uint256 i = 0; i < swaps.length; i++) {
            Swap memory swap = swaps[i];
            IXToken swapTokenIn = IXToken(swap.tokenIn);
            IBPool pool = IBPool(swap.pool);

            if (swapTokenIn.allowance(address(this), swap.pool) > 0) {
                swapTokenIn.approve(swap.pool, 0);
            }
            swapTokenIn.approve(swap.pool, swap.swapAmount);

            (uint256 tokenAmountOut, ) =
                pool.swapExactAmountIn(
                    swap.tokenIn,
                    swap.swapAmount,
                    swap.tokenOut,
                    swap.limitReturnAmount,
                    swap.maxPrice
                );
            totalAmountOut = tokenAmountOut.add(totalAmountOut);
        }

        require(totalAmountOut >= minTotalAmountOut, "ERR_LIMIT_OUT");

        transferFeeFrom(tokenIn, protocolFee.batchFee(swaps, totalAmountIn), useUtilityToken);

        transfer(tokenOut, totalAmountOut);
        transfer(tokenIn, getBalance(tokenIn));
    }


/**
 * @dev Execute multi-hop swaps returned from off-chain SOR for swapExactIn trade type.
 *
 * @param swapSequences multi-hop swaps sequence.
 * @param tokenIn Input token.
 * @param tokenOut Output token.
 * @param totalAmountIn Total amount of tokenIn.
 * @param minTotalAmountOut Minumum amount of tokenOut.
 * @param useUtilityToken Flag to determine if the protocol swap fee is paid using UtilityToken or TokenIn.
 */

function multihopBatchSwapExactIn(
        Swap[][] memory swapSequences,
        IXToken tokenIn,
        IXToken tokenOut,
        uint256 totalAmountIn,
        uint256 minTotalAmountOut,
        bool useUtilityToken
    ) external returns (uint256 totalAmountOut) {
        transferFrom(tokenIn, totalAmountIn);

        for (uint256 i = 0; i < swapSequences.length; i++) {
            uint256 tokenAmountOut;
            for (uint256 k = 0; k < swapSequences[i].length; k++) {
                Swap memory swap = swapSequences[i][k];
                IXToken swapTokenIn = IXToken(swap.tokenIn);
                if (k == 1) {
                    // Makes sure that on the second swap the output of the first was used
                    // so there is not intermediate token leftover
                    swap.swapAmount = tokenAmountOut;
                }

                IBPool pool = IBPool(swap.pool);
                if (swapTokenIn.allowance(address(this), swap.pool) > 0) {
                    swapTokenIn.approve(swap.pool, 0);
                }
                swapTokenIn.approve(swap.pool, swap.swapAmount);
                (tokenAmountOut, ) = pool.swapExactAmountIn(
                    swap.tokenIn,
                    swap.swapAmount,
                    swap.tokenOut,
                    swap.limitReturnAmount,
                    swap.maxPrice
                );
            }
            // This takes the amountOut of the last swap
            totalAmountOut = tokenAmountOut.add(totalAmountOut);
        }

        require(totalAmountOut >= minTotalAmountOut, "ERR_LIMIT_OUT");

        transferFeeFrom(tokenIn, protocolFee.multihopBatch(swapSequences, totalAmountIn), useUtilityToken);

        transfer(tokenOut, totalAmountOut);
        transfer(tokenIn, getBalance(tokenIn));
    }


/**
 * @dev Used for swaps returned from viewSplit function.
 *
 * @param tokenIn Input token.
 * @param tokenOut Output token.
 * @param totalAmountIn Total amount of tokenIn.
 * @param minTotalAmountOut Minumum amount of tokenOut.
 * @param nPools Maximum mumber of pools.
 * @param useUtilityToken Flag to determine if the protocol swap fee is paid using UtilityToken or TokenIn.
 */

function smartSwapExactIn(
        IXToken tokenIn,
        IXToken tokenOut,
        uint256 totalAmountIn,
        uint256 minTotalAmountOut,
        uint256 nPools,
        bool useUtilityToken
    ) external returns (uint256 totalAmountOut) {
        Swap[] memory swaps;
        uint256 totalOutput;
        (swaps, totalOutput) = viewSplitExactIn(address(tokenIn), address(tokenOut), totalAmountIn, nPools);

        require(totalOutput >= minTotalAmountOut, "ERR_LIMIT_OUT");

        totalAmountOut = batchSwapExactIn(swaps, tokenIn, tokenOut, totalAmountIn, minTotalAmountOut, useUtilityToken);
    }


/**
 * @dev Join the `pool`, getting `poolAmountOut` pool tokens. This will pull some of each of the currently
 * trading tokens in the pool, meaning you must have called approve for each token for this pool. These
 * values are limited by the array of `maxAmountsIn` in the order of the pool tokens.
 *
 * @param pool Pool address.
 * @param poolAmountOut Exact pool amount out.
 * @param maxAmountsIn Maximum amounts in.
 */

function joinPool(
        address pool,
        uint256 poolAmountOut,
        uint256[] calldata maxAmountsIn
    ) external {
        address[] memory tokens = IBPool(pool).getCurrentTokens();

        // pull xTokens
        for (uint256 i = 0; i < tokens.length; i++) {
            transferFrom(IXToken(tokens[i]), maxAmountsIn[i]);
            IXToken(tokens[i]).approve(pool, maxAmountsIn[i]);
        }

        IBPool(pool).joinPool(poolAmountOut, maxAmountsIn);

        // push remaining xTokens
        for (uint256 i = 0; i < tokens.length; i++) {
            transfer(IXToken(tokens[i]), getBalance(IXToken(tokens[i])));
        }

        // Wrap balancer liquidity tokens into its representing xToken
        IBPool(pool).approve(address(xTokenWrapper), poolAmountOut);
        require(xTokenWrapper.wrap(pool, poolAmountOut), "ERR_WRAP_POOL");

        transfer(IXToken(xTokenWrapper.tokenToXToken(pool)), poolAmountOut);

        emit JoinPool(msg.sender, pool,  poolAmountOut);
    }


/**
 * @dev Exit the pool, paying poolAmountIn pool tokens and getting some of each of the currently trading
 * tokens in return. These values are limited by the array of minAmountsOut in the order of the pool tokens.
 *
 * @param pool Pool address.
 * @param poolAmountIn Exact pool amount int.
 * @param minAmountsOut Minumum amounts out.
 */

function exitPool(
        address pool,
        uint256 poolAmountIn,
        uint256[] calldata minAmountsOut
    ) external {
        address wrappedLPT = xTokenWrapper.tokenToXToken(pool);

        // pull wrapped liquitity tokens
        transferFrom(IXToken(wrappedLPT), poolAmountIn);

        // unwrap wrapped liquitity tokens
        require(xTokenWrapper.unwrap(wrappedLPT, poolAmountIn), "ERR_UNWRAP_POOL");

        // LPT do not need to be approved when exit
        IBPool(pool).exitPool(poolAmountIn, minAmountsOut);

        // push xTokens
        address[] memory tokens = IBPool(pool).getCurrentTokens();
        for (uint256 i = 0; i < tokens.length; i++) {
            transfer(IXToken(tokens[i]), getBalance(IXToken(tokens[i])));
        }

        emit ExitPool(msg.sender, pool, poolAmountIn);
    }


/**
 * @dev Pay `tokenAmountIn` of token `tokenIn` to join the pool, getting `poolAmountOut` of the pool shares.
 *
 * @param pool Pool address.
 * @param tokenIn Input token.
 * @param tokenAmountIn Exact amount of tokenIn to pay.
 * @param minPoolAmountOut Minumum amount of pool shares to get.
 */

function joinswapExternAmountIn(
        address pool,
        address tokenIn,
        uint256 tokenAmountIn,
        uint256 minPoolAmountOut
    ) external returns (uint256 poolAmountOut) {
        // pull xToken
        transferFrom(IXToken(tokenIn), tokenAmountIn);
        IXToken(tokenIn).approve(pool, tokenAmountIn);

        poolAmountOut = IBPool(pool).joinswapExternAmountIn(tokenIn, tokenAmountIn, minPoolAmountOut);

        // Wrap balancer liquidity tokens into its representing xToken
        IBPool(pool).approve(address(xTokenWrapper), poolAmountOut);
        require(xTokenWrapper.wrap(pool, poolAmountOut), "ERR_WRAP_POOL");

        transfer(IXToken(xTokenWrapper.tokenToXToken(pool)), poolAmountOut);

        emit JoinPool(msg.sender, pool,  poolAmountOut);
    }


/**
 * @dev Specify `poolAmountOut` pool shares that you want to get, and a token `tokenIn` to pay with.
 * This costs `tokenAmountIn` tokens (these went into the pool).
 *
 * @param pool Pool address.
 * @param tokenIn Input token.
 * @param poolAmountOut Exact amount of pool shares to get.
 * @param maxAmountIn Minumum amount of tokenIn to pay.
 */

function joinswapPoolAmountOut(
        address pool,
        address tokenIn,
        uint256 poolAmountOut,
        uint256 maxAmountIn
    ) external returns (uint256 tokenAmountIn) {
        // pull xToken
        transferFrom(IXToken(tokenIn), maxAmountIn);
        IXToken(tokenIn).approve(pool, maxAmountIn);

        tokenAmountIn = IBPool(pool).joinswapPoolAmountOut(tokenIn, poolAmountOut, maxAmountIn);

        // push remaining xTokens
        transfer(IXToken(tokenIn), getBalance(IXToken(tokenIn)));

        // Wrap balancer liquidity tokens into its representing xToken
        IBPool(pool).approve(address(xTokenWrapper), poolAmountOut);
        require(xTokenWrapper.wrap(pool, poolAmountOut), "ERR_WRAP_POOL");

        transfer(IXToken(xTokenWrapper.tokenToXToken(pool)), poolAmountOut);

        emit JoinPool(msg.sender, pool,  poolAmountOut);
    }


/**
 * @dev Pay `poolAmountIn` pool shares into the pool, getting `tokenAmountOut` of the given
 * token `tokenOut` out of the pool.
 *
 * @param pool Pool address.
 * @param tokenOut Input token.
 * @param poolAmountIn Exact amount of pool shares to pay.
 * @param minAmountOut Minumum amount of tokenIn to get.
 */

function exitswapPoolAmountIn(
        address pool,
        address tokenOut,
        uint256 poolAmountIn,
        uint256 minAmountOut
    ) external returns (uint256 tokenAmountOut) {
        address wrappedLPT = xTokenWrapper.tokenToXToken(pool);

        // pull wrapped liquitity tokens
        transferFrom(IXToken(wrappedLPT), poolAmountIn);

        // unwrap wrapped liquitity tokens
        require(xTokenWrapper.unwrap(wrappedLPT, poolAmountIn), "ERR_UNWRAP_POOL");

        // LPT do not need to be approved when exit
        tokenAmountOut = IBPool(pool).exitswapPoolAmountIn(tokenOut, poolAmountIn, minAmountOut);

        // push xToken
        transfer(IXToken(tokenOut), tokenAmountOut);

        emit ExitPool(msg.sender, pool, poolAmountIn);
    }


/**
 * @dev Specify tokenAmountOut of token tokenOut that you want to get out of the pool.
 * This costs poolAmountIn pool shares (these went into the pool).
 *
 * @param pool Pool address.
 * @param tokenOut Input token.
 * @param tokenAmountOut Exact amount of of tokenIn to get.
 * @param maxPoolAmountIn Maximum amount of pool shares to pay.
 */

function exitswapExternAmountOut(
        address pool,
        address tokenOut,
        uint256 tokenAmountOut,
        uint256 maxPoolAmountIn
    ) external returns (uint256 poolAmountIn) {
        address wrappedLPT = xTokenWrapper.tokenToXToken(pool);

        // pull wrapped liquitity tokens
        transferFrom(IXToken(wrappedLPT), maxPoolAmountIn);

        // unwrap wrapped liquitity tokens
        require(xTokenWrapper.unwrap(wrappedLPT, maxPoolAmountIn), "ERR_UNWRAP_POOL");

        // LPT do not need to be approved when exit
        poolAmountIn = IBPool(pool).exitswapExternAmountOut(tokenOut, tokenAmountOut, maxPoolAmountIn);

        // push xToken
        transfer(IXToken(tokenOut), tokenAmountOut);

        uint256 remainingLPT = maxPoolAmountIn.sub(poolAmountIn);
        if (remainingLPT > 0) {
            // Wrap remaining balancer liquidity tokens into its representing xToken
            IBPool(pool).approve(address(xTokenWrapper), remainingLPT);
            require(xTokenWrapper.wrap(pool, remainingLPT), "ERR_WRAP_POOL");

            transfer(IXToken(wrappedLPT), remainingLPT);
        }

        emit ExitPool(msg.sender, pool, poolAmountIn);
    }


/**
 * @dev View function that calculates most optimal swaps (exactIn swap type) across a max of nPools.
 * Returns an array of `Swaps` and the total amount out for swap.
 *
 * @param tokenIn Input token.
 * @param tokenOut Output token.
 * @param swapAmount Amount of tokenIn.
 * @param nPools Maximum mumber of pools.
 */

function viewSplitExactIn(
        address tokenIn,
        address tokenOut,
        uint256 swapAmount,
        uint256 nPools
    ) public view returns (Swap[] memory swaps, uint256 totalOutput) {
        address[] memory poolAddresses = registry.getBestPoolsWithLimit(tokenIn, tokenOut, nPools);

        Pool[] memory pools = new Pool[](poolAddresses.length);
        uint256 sumEffectiveLiquidity;
        for (uint256 i = 0; i < poolAddresses.length; i++) {
            pools[i] = getPoolData(tokenIn, tokenOut, poolAddresses[i]);
            sumEffectiveLiquidity = sumEffectiveLiquidity.add(pools[i].effectiveLiquidity);
        }

        uint256[] memory bestInputAmounts = new uint256[](pools.length);
        uint256 totalInputAmount;
        for (uint256 i = 0; i < pools.length; i++) {
            bestInputAmounts[i] = swapAmount.mul(pools[i].effectiveLiquidity).div(sumEffectiveLiquidity);
            totalInputAmount = totalInputAmount.add(bestInputAmounts[i]);
        }

        if (totalInputAmount < swapAmount) {
            bestInputAmounts[0] = bestInputAmounts[0].add(swapAmount.sub(totalInputAmount));
        } else {
            bestInputAmounts[0] = bestInputAmounts[0].sub(totalInputAmount.sub(swapAmount));
        }

        swaps = new Swap[](pools.length);

        for (uint256 i = 0; i < pools.length; i++) {
            swaps[i] = Swap({
                pool: pools[i].pool,
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                swapAmount: bestInputAmounts[i],
                limitReturnAmount: 0,
                maxPrice: uint256(-1)
            });
        }

        totalOutput = calcTotalOutExactIn(bestInputAmounts, pools);

        return (swaps, totalOutput);
    }


/**
 * @dev Trtansfers protocol swap fee from the sender to this `feeReceiver`.
 *
 */

function transferFeeFrom(
        IXToken token,
        uint256 amount,
        bool useUtitlityToken
    ) internal {
        if (useUtitlityToken && utilityToken != address(0) && address(utilityTokenFeed) != address(0)) {
            uint256 discountedFee = utilityTokenFeed.calculateAmount(address(token), amount.div(2));

            if (discountedFee > 0) {
                require(
                    IERC20(utilityToken).transferFrom(msg.sender, feeReceiver, discountedFee),
                    "ERR_FEE_UTILITY_TRANSFER_FAILED"
                );
            } else {
                require(token.transferFrom(msg.sender, feeReceiver, amount), "ERR_FEE_TRANSFER_FAILED");
            }
        } else {
            require(token.transferFrom(msg.sender, feeReceiver, amount), "ERR_FEE_TRANSFER_FAILED");
        }
    }


// Saftey Checks for Divison Tasks

function div(uint256 a, uint256 b) internal constant returns (uint256) {
    assert(b > 0);
    uint256 c = a / b;
    assert(a == b * c + a % b);
    return c;
  }


/**
 * @dev Transfer tokens. Make sure that both participants have no open dividends left.
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 */

function _transfer(address payable from, address payable to, uint256 value) internal
    {
        require(value > 0, "Transferred value has to be grater than 0.");
        require(to != address(0), "0x00 address not allowed.");
        require(value <= balanceOf[from], "Not enough funds on sender address.");
        require(balanceOf[to] + value >= balanceOf[to], "Overflow protection.");

        uint256 fromOwing = dividendBalanceOf(from);
        uint256 toOwing = dividendBalanceOf(to);

        if (tradables[from] == true && (tradables[to] == true || toOwing == 0))
        {

            internalClaimDividend(from);
            internalClaimDividend(to);
        } else {

            require(fromOwing == 0 && toOwing == 0, "Unclaimed dividends on sender and/or receiver");
        }

        balanceOf[from] = balanceOf[from].sub(value);
        balanceOf[to] = balanceOf[to].add(value);

        lastDividends[to] = lastDividends[from];    // In case of new account, set lastDividends of receiver to totalDividends.

        emit Transfer(from, to, value);
    }


// --- Init ---

function initialize() public {

        require(!initialized, "initialize: Already initialized!");
        _governance = msg.sender;
        _pID = 0;
        _totalReferReward = 0;
        _totalRegisterCount = 0;
        _refer1RewardRate = 700; //7%
        _refer2RewardRate = 300; //3%
        _baseRate = 10000;
        _registrationBaseFee = 100 finney;
        _registrationStep = 100;
        _stepFee = 100 finney;
        _defaulRefer ="cocos";
        _teamWallet = msg.sender;
        addDefaultPlayer(_teamWallet, _defaulRefer);
        initialized = true;
    }


/**
 * resolve the refer's reward from a player
 */

function settleReward(address from, uint256 amount)
        isRegisteredPool()
        validAddress(from)
        external
        returns (uint256)
    {
         // set up our tx event data and determine if player is new or not
        _determinePID(from);

        uint256 pID = _pIDxAddr[from];
        uint256 affID = _plyr[pID].laff;

        if(affID <= 0 ){
            affID = _pIDxName[_defaulRefer];
            _plyr[pID].laff = affID;
        }

        if(amount <= 0){
            return 0;
        }

        uint256 fee = 0;

        // father
        uint256 affReward = (amount.mul(_refer1RewardRate)).div(_baseRate);
        _plyr[affID].rreward = _plyr[affID].rreward.add(affReward);
        _totalReferReward = _totalReferReward.add(affReward);
        fee = fee.add(affReward);

        // grandfather
        uint256 aff_affID = _plyr[affID].laff;
        uint256 aff_affReward = amount.mul(_refer2RewardRate).div(_baseRate);
        if(aff_affID <= 0){
            aff_affID = _pIDxName[_defaulRefer];
        }
        _plyr[aff_affID].rreward = _plyr[aff_affID].rreward.add(aff_affReward);
        _totalReferReward= _totalReferReward.add(aff_affReward);

        _plyr[pID].amount = _plyr[pID].amount.add( amount);

        fee = fee.add(aff_affReward);

        emit eveSettle( pID,affID,aff_affID,affReward,aff_affReward,amount);

        return fee;
    }


/**
 * claim all of the refer reward.
 */

function claim()
        public
    {
        address addr = msg.sender;
        uint256 pid = _pIDxAddr[addr];
        uint256 reward = _plyr[pid].rreward;

        require(reward > 0,"only have reward");

        //reset
        _plyr[pid].allReward = _plyr[pid].allReward.add(reward);
        _plyr[pid].rreward = 0;

        //get reward
        _cocos.safeTransfer(addr, reward);

        // fire event
        emit eveClaim(_pIDxAddr[addr], addr, reward, balances());
    }


/**
 * @dev add a default player
 */

function addDefaultPlayer(address addr, bytes32 name)
        private
    {
        _pID++;

        _plyr[_pID].addr = addr;
        _plyr[_pID].name = name;
        _plyr[_pID].nameCount = 1;
        _pIDxAddr[addr] = _pID;
        _pIDxName[name] = _pID;
        _plyrNames[_pID][name] = true;

        //fire event
        emit eveDefaultPlayer(_pID,addr,name);
    }


/**
 * @dev bind a refer,if affcode invalid, use default refer
 */

function bindRefer( address from, string calldata  affCode )
        isRegisteredPool()
        external
        returns (bool)
    {

        bytes memory tempCode = bytes(affCode);
        bytes32 affName = 0x0;
        if (tempCode.length >= 0) {
            assembly {
                affName := mload(add(tempCode, 32))
            }
        }

        return _bindRefer(from, affName);
    }


//  get total count tokens (to calculate profit for one token)

function get_CountProfitsToken() constant returns(uint256){
		uint256 countProfitsTokens = 0;

		mapping(address => bool) uniqueHolders;

		uint256 countHolders = smartToken.getCountHolders();
		for(uint256 i=0; i<countHolders; i++)
		{
			address holder = smartToken.getItemHolders(i);
			if(holder!=address(0x0) && !uniqueHolders[holder])
			{
				uint256 holdersTokens = smartToken.balanceOf(holder);
				if(holdersTokens>0)
				{
					uint256 tempTokens = smartToken.tempTokensBalanceOf(holder);
					if((holdersTokens+tempTokens)/decimal >= tokensNeededToGetPayment)
					{
						uniqueHolders[holder]=true;
						countProfitsTokens += (holdersTokens+tempTokens);
					}
				}
			}
		}

		uint256 countTempHolders = smartToken.getCountTempHolders();
		for(uint256 j=0; j<countTempHolders; j++)
		{
			address temp_holder = smartToken.getItemTempHolders(j);
			if(temp_holder!=address(0x0) && !uniqueHolders[temp_holder])
			{
				uint256 token_balance = smartToken.balanceOf(temp_holder);
				if(token_balance==0)
				{
					uint256 count_tempTokens = smartToken.tempTokensBalanceOf(temp_holder);
					if(count_tempTokens>0 && count_tempTokens/decimal >= tokensNeededToGetPayment)
					{
						uniqueHolders[temp_holder]=true;
						countProfitsTokens += count_tempTokens;
					}
				}
			}
		}

		return countProfitsTokens;
	}


// get holders addresses to make payment each of them

function get_Holders(uint256 position) constant returns(address[64] listHolders, uint256 nextPosition)
	{
		uint8 n = 0;
		uint256 countHolders = smartToken.getCountHolders();
		for(; position < countHolders; position++){
			address holder = smartToken.getItemHolders(position);
			if(holder!=address(0x0)){
				uint256 holdersTokens = smartToken.balanceOf(holder);
				if(holdersTokens>0){
					uint256 tempTokens = smartToken.tempTokensBalanceOf(holder);
					if((holdersTokens+tempTokens)/decimal >= tokensNeededToGetPayment){
						//
						listHolders[n++] = holder;
						if (n == 64)
						{
							nextPosition = position + 1;
							return;
						}
					}
				}
			}
		}

		if (position >= countHolders)
		{
			uint256 countTempHolders = smartToken.getCountTempHolders();
			for(uint256 j=position-countHolders; j<countTempHolders; j++)
			{
				address temp_holder = smartToken.getItemTempHolders(j);
				if(temp_holder!=address(0x0)){
					uint256 token_balance = smartToken.balanceOf(temp_holder);
					if(token_balance==0){
						uint256 count_tempTokens = smartToken.tempTokensBalanceOf(temp_holder);
						if(count_tempTokens>0 && count_tempTokens/decimal >= tokensNeededToGetPayment){
							listHolders[n++] = temp_holder;
							if (n == 64)
							{
								nextPosition = position + 1;
								return;
							}
						}
					}
				}

				position = position + 1;
			}
		}

		nextPosition = 0;
	}


// Get profit for specified token holder
// Function should be executed in blockDividend ! (see struct DividendInfo)
// Don't call this function via etherescan.io
// Example how to call via JavaScript and web3
// var abiDividend = [...];
// var holderAddress = "0xdd94ddf50485f41491c415e7133100e670cd4ef3";
// var dividendIndex = 1;       // starts from zero
// var blockDividend = 3527958; // see function getDividendInfo
// web3.eth.contract(abiDividend).at("0x600e18779b6aC789b95a12C30b5863E842F4ae1d").get_HoldersProfit(dividendIndex, holderAddress, blockDividend, function(err, profit){
//    alert("Your profit " + web3.fromWei(profit).toString(10) + "ETH");
// });

function get_HoldersProfit(uint256 dividendPaymentNum, address holder) constant returns(uint256){
		uint256 profit = 0;
		if(holder != address(0x0) && dividendHistory.length > 0 && dividendPaymentNum < dividendHistory.length){
			uint256 count_tokens = smartToken.balanceOf(holder) + smartToken.tempTokensBalanceOf(holder);
			if(count_tokens/decimal >= tokensNeededToGetPayment){
				profit = (count_tokens*dividendHistory[dividendPaymentNum].amountDividend)/get_CountProfitsToken();
			}
		}
		return profit;
	}


//CONSTANT HELPER FUNCTIONS

function getBankroll()
        constant
        returns(uint) {

        if ((invested < investorsProfit) ||
            (invested + investorsProfit < invested) ||
            (invested + investorsProfit < investorsLosses)) {
            return 0;
        }
        else {
            return invested + investorsProfit - investorsLosses;
        }
    }


/* Allocate Tokens for MAR */

function allocateMARTokens() public {
        require(msg.sender==tokenIssuer);
        uint tokens = 0;

        if(block.timestamp > month6Unlock && !month6Allocated)
        {
            month6Allocated = true;
            tokens = safeDiv(totalTokensMAR, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);

        }
        else if(block.timestamp > month12Unlock && !month12Allocated)
        {
            month12Allocated = true;
            tokens = safeDiv(totalTokensMAR, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);

        }
        else if(block.timestamp > month24Unlock && !month24Allocated)
        {
            month24Allocated = true;
            tokens = safeDiv(totalTokensMAR, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);

        }
        else if(block.timestamp > month36Unlock && !month36Allocated)
        {
            month36Allocated = true;
            tokens = safeDiv(totalTokensMAR, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);
        }
        else if(block.timestamp > month48Unlock && !month48Allocated)
        {
            month48Allocated = true;
            tokens = safeDiv(totalTokensMAR, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);
        }
        else revert();

        AllocateMARTokens(msg.sender);
    }


/* Allocate Tokens for DAPP */

function allocateDAPPTokens() public {
        require(msg.sender==tokenIssuer);
        uint tokens = 0;

        if(block.timestamp > month9Unlock && !month9Allocated)
        {
            month9Allocated = true;
            tokens = safeDiv(totalTokensDAPP, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);
        }
        else if(block.timestamp > month18Unlock && !month18Allocated)
        {
            month18Allocated = true;
            tokens = safeDiv(totalTokensDAPP, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);

        }
        else if(block.timestamp > month27Unlock && !month27Allocated)
        {
            month27Allocated = true;
            tokens = safeDiv(totalTokensDAPP, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);

        }
        else if(block.timestamp > month36Unlock && !month36AllocatedDAPP)
        {
            month36AllocatedDAPP = true;
            tokens = safeDiv(totalTokensDAPP, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);
        }
        else if(block.timestamp > month45Unlock && !month45Allocated)
        {
            month45Allocated = true;
            tokens = safeDiv(totalTokensDAPP, 5);
            balances[tokenIssuer] = safeAdd(balances[tokenIssuer], tokens);
            totalSupply = safeAdd(totalSupply, tokens);
        }
        else revert();

        AllocateDAPPTokens(msg.sender);
    }


/* Mint Token */

function mintTokens(address tokenHolder, uint256 amountToken) public
    returns (bool success)
    {
        require(msg.sender==tokenIssuer);

        if(totalTokenSaled + amountToken <= totalTokensCrowdSale + totalTokensReward)
        {
            balances[tokenHolder] = safeAdd(balances[tokenHolder], amountToken);
            totalTokenSaled = safeAdd(totalTokenSaled, amountToken);
            totalSupply = safeAdd(totalSupply, amountToken);
            TokenMint(tokenHolder, amountToken);
            return true;
        }
        else
        {
            return false;
        }
    }


/**
 * @dev Approves the token to the spender address with allowance amount.
 * @notice Approves the token to the spender address with allowance amount.
 * @param token_ token for which allowance is to be given.
 * @param spender_ the address to which the allowance is to be given.
 * @param amount_ amount of token.
 */

function approve(
        address token_,
        address spender_,
        uint256 amount_
    ) internal {
        TokenInterface tokenContract_ = TokenInterface(token_);
        try tokenContract_.approve(spender_, amount_) {} catch {
            IERC20 token = IERC20(token_);
            token.safeApprove(spender_, 0);
            token.safeApprove(spender_, amount_);
        }
    }


/**
 * @dev Approves the tokens to the receiver address with allowance (amount + fee).
 * @notice Approves the tokens to the receiver address with allowance (amount + fee).
 * @param _instaLoanVariables struct which includes list of token addresses and amounts.
 * @param _fees list of premiums/fees for the corresponding addresses for flashloan.
 * @param _receiver address to which tokens have to be approved.
 */

function safeApprove(
        FlashloanVariables memory _instaLoanVariables,
        uint256[] memory _fees,
        address _receiver
    ) internal {
        uint256 length_ = _instaLoanVariables._tokens.length;
        require(
            length_ == _instaLoanVariables._amounts.length,
            "Lengths of parameters not same"
        );
        require(length_ == _fees.length, "Lengths of parameters not same");
        for (uint256 i = 0; i < length_; i++) {
            approve(
                _instaLoanVariables._tokens[i],
                _receiver,
                _instaLoanVariables._amounts[i] + _fees[i]
            );
        }
    }


/**
 * @dev Transfers the tokens to the receiver address.
 * @notice Transfers the tokens to the receiver address.
 * @param _instaLoanVariables struct which includes list of token addresses and amounts.
 * @param _receiver address to which tokens have to be transferred.
 */

function safeTransfer(
        FlashloanVariables memory _instaLoanVariables,
        address _receiver
    ) internal {
        uint256 length_ = _instaLoanVariables._tokens.length;
        require(
            length_ == _instaLoanVariables._amounts.length,
            "Lengths of parameters not same"
        );
        for (uint256 i = 0; i < length_; i++) {
            IERC20 token = IERC20(_instaLoanVariables._tokens[i]);
            token.safeTransfer(_receiver, _instaLoanVariables._amounts[i]);
        }
    }


/**
 * @dev Calculates the balances..
 * @notice Calculates the balances of the account passed for the tokens.
 * @param _tokens list of token addresses to calculate balance for.
 * @param _account account to calculate balance for.
 */

function calculateBalances(address[] memory _tokens, address _account)
        internal
        view
        returns (uint256[] memory)
    {
        uint256 _length = _tokens.length;
        uint256[] memory balances_ = new uint256[](_length);
        for (uint256 i = 0; i < _length; i++) {
            IERC20 token = IERC20(_tokens[i]);
            balances_[i] = token.balanceOf(_account);
        }
        return balances_;
    }


/**
 * @dev Supply tokens for the amounts to compound.
 * @notice Supply tokens for the amounts to compound.
 * @param _tokens token addresses.
 * @param _amounts amounts of tokens.
 */

function compoundSupply(address[] memory _tokens, uint256[] memory _amounts)
        internal
    {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        address[] memory cTokens_ = new address[](length_);
        for (uint256 i = 0; i < length_; i++) {
            if (_tokens[i] == address(wethToken)) {
                wethToken.withdraw(_amounts[i]);
                CEthInterface cEth_ = CEthInterface(cethTokenAddr);
                cEth_.mint{value: _amounts[i]}();
                cTokens_[i] = cethTokenAddr;
            } else {
                CTokenInterface cToken_ = CTokenInterface(
                    tokenToCToken[_tokens[i]]
                );
                // Approved already in addTokenToctoken function
                require(cToken_.mint(_amounts[i]) == 0, "mint failed");
                cTokens_[i] = tokenToCToken[_tokens[i]];
            }
        }
    }


/**
 * @dev Withdraw tokens from compound.
 * @notice Withdraw tokens from compound.
 * @param _tokens token addresses.
 * @param _amounts amounts of tokens.
 */

function compoundWithdraw(
        address[] memory _tokens,
        uint256[] memory _amounts
    ) internal {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        for (uint256 i = 0; i < length_; i++) {
            if (_tokens[i] == address(wethToken)) {
                CEthInterface cEth_ = CEthInterface(cethTokenAddr);
                require(
                    cEth_.redeemUnderlying(_amounts[i]) == 0,
                    "redeem failed"
                );
                wethToken.deposit{value: _amounts[i]}();
            } else {
                CTokenInterface cToken_ = CTokenInterface(
                    tokenToCToken[_tokens[i]]
                );
                require(
                    cToken_.redeemUnderlying(_amounts[i]) == 0,
                    "redeem failed"
                );
            }
        }
    }


/**
 * @dev Supply tokens to aave.
 * @notice Supply tokens to aave.
 * @param _tokens token addresses.
 * @param _amounts amounts of tokens.
 */

function aaveSupply(address[] memory _tokens, uint256[] memory _amounts)
        internal
    {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        for (uint256 i = 0; i < length_; i++) {
            approve(_tokens[i], address(aaveLending), _amounts[i]);
            aaveLending.deposit(_tokens[i], _amounts[i], address(this), 3228);
            aaveLending.setUserUseReserveAsCollateral(_tokens[i], true);
        }
    }


/**
 * @dev Borrow tokens from aave.
 * @notice Borrow tokens from aave.
 * @param _tokens list of token addresses.
 * @param _amounts list of amounts for respective tokens.
 */

function aaveBorrow(address[] memory _tokens, uint256[] memory _amounts)
        internal
    {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        for (uint256 i = 0; i < length_; i++) {
            aaveLending.borrow(_tokens[i], _amounts[i], 2, 3228, address(this));
        }
    }


/**
 * @dev Payback tokens to aave.
 * @notice Payback tokens to aave.
 * @param _tokens list of token addresses.
 * @param _amounts list of amounts for respective tokens.
 */

function aavePayback(address[] memory _tokens, uint256[] memory _amounts)
        internal
    {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        for (uint256 i = 0; i < length_; i++) {
            approve(_tokens[i], address(aaveLending), _amounts[i]);
            aaveLending.repay(_tokens[i], _amounts[i], 2, address(this));
        }
    }


/**
 * @dev Withdraw tokens from aave.
 * @notice Withdraw tokens from aave.
 * @param _tokens token addresses.
 * @param _amounts amounts of tokens.
 */

function aaveWithdraw(address[] memory _tokens, uint256[] memory _amounts)
        internal
    {
        uint256 length_ = _tokens.length;
        require(_amounts.length == length_, "array-lengths-not-same");
        for (uint256 i = 0; i < length_; i++) {
            aaveLending.withdraw(_tokens[i], _amounts[i], address(this));
        }
    }


/**
 * @dev Returns fee for the passed route in BPS.
 * @notice Returns fee for the passed route in BPS. 1 BPS == 0.01%.
 * @param _route route number for flashloan.
 */

function calculateFeeBPS(uint256 _route, address account_)
        public
        view
        returns (uint256 BPS_)
    {
        if (_route == 1) {
            BPS_ = aaveLending.FLASHLOAN_PREMIUM_TOTAL();
        } else if (_route == 2 || _route == 3 || _route == 4) {
            BPS_ = (makerLending.toll()) / (10**14);
        } else if (_route == 5 || _route == 6 || _route == 7) {
            BPS_ =
                (
                    balancerLending
                        .getProtocolFeesCollector()
                        .getFlashLoanFeePercentage()
                ) *
                100;
        } else {
            revert("Invalid source");
        }

        if (!isWhitelisted[account_] && BPS_ < InstaFeeBPS) {
            BPS_ = InstaFeeBPS;
        }
    }


/**
 * @dev Calculate fees for the respective amounts and fee in BPS passed.
 * @notice Calculate fees for the respective amounts and fee in BPS passed. 1 BPS == 0.01%.
 * @param _amounts list of amounts.
 * @param _BPS fee in BPS.
 */

function calculateFees(uint256[] memory _amounts, uint256 _BPS)
        internal
        pure
        returns (uint256[] memory)
    {
        uint256 length_ = _amounts.length;
        uint256[] memory InstaFees = new uint256[](length_);
        for (uint256 i = 0; i < length_; i++) {
            InstaFees[i] = (_amounts[i] * _BPS) / (10**4);
        }
        return InstaFees;
    }


/**
 * @dev Sort the tokens and amounts arrays according to token addresses.
 * @notice Sort the tokens and amounts arrays according to token addresses.
 * @param _tokens list of token addresses.
 * @param _amounts list of respective amounts.
 */

function bubbleSort(address[] memory _tokens, uint256[] memory _amounts)
        internal
        pure
        returns (address[] memory, uint256[] memory)
    {
        for (uint256 i = 0; i < _tokens.length - 1; i++) {
            for (uint256 j = 0; j < _tokens.length - i - 1; j++) {
                if (_tokens[j] > _tokens[j + 1]) {
                    (
                        _tokens[j],
                        _tokens[j + 1],
                        _amounts[j],
                        _amounts[j + 1]
                    ) = (
                        _tokens[j + 1],
                        _tokens[j],
                        _amounts[j + 1],
                        _amounts[j]
                    );
                }
            }
        }
        return (_tokens, _amounts);
    }


/**
 * Constructor - instantiates token supply and allocates balanace of
 * to the owner (msg.sender).
 */

function QuantstampToken(address _admin) {
        // the owner is a custodian of tokens that can
        // give an allowance of tokens for crowdsales
        // or to the admin, but cannot itself transfer
        // tokens; hence, this requirement
        require(msg.sender != _admin);

        totalSupply = INITIAL_SUPPLY;
        crowdSaleAllowance = CROWDSALE_ALLOWANCE;
        adminAllowance = ADMIN_ALLOWANCE;

        // mint all tokens
        balances[msg.sender] = totalSupply;
        Transfer(address(0x0), msg.sender, totalSupply);

        adminAddr = _admin;
        approve(adminAddr, adminAllowance);
    }


/**
 * Associates this token with a current crowdsale, giving the crowdsale
 * an allowance of tokens from the crowdsale supply. This gives the
 * crowdsale the ability to call transferFrom to transfer tokens to
 * whomever has purchased them.
 *
 * Note that if _amountForSale is 0, then it is assumed that the full
 * remaining crowdsale supply is made available to the crowdsale.
 *
 * @param _crowdSaleAddr The address of a crowdsale contract that will sell this token
 * @param _amountForSale The supply of tokens provided to the crowdsale
 */

function setCrowdsale(address _crowdSaleAddr, uint256 _amountForSale) external onlyOwner {
        require(!transferEnabled);
        require(_amountForSale <= crowdSaleAllowance);

        // if 0, then full available crowdsale supply is assumed
        uint amount = (_amountForSale == 0) ? crowdSaleAllowance : _amountForSale;

        // Clear allowance of old, and set allowance of new
        approve(crowdSaleAddr, 0);
        approve(_crowdSaleAddr, amount);

        crowdSaleAddr = _crowdSaleAddr;
    }


/**
 * Overrides ERC20 transferFrom function with modifier that prevents the
 * ability to transfer tokens until after transfers have been enabled.
 */

function transferFrom(address _from, address _to, uint256 _value) public onlyWhenTransferEnabled validDestination(_to) returns (bool) {
        bool result = super.transferFrom(_from, _to, _value);
        if (result) {
            if (msg.sender == crowdSaleAddr)
                crowdSaleAllowance = crowdSaleAllowance.sub(_value);
            if (msg.sender == adminAddr)
                adminAllowance = adminAllowance.sub(_value);
        }
        return result;
    }


/**
 * @dev Overload of {ECDSA-recover} that receives the `r` and `vs` short-signature fields separately.
 *
 * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
 *
 * _Available since v4.2._
 */

function recover(
        bytes32 hash,
        bytes32 r,
        bytes32 vs
    ) internal pure returns (address) {
        bytes32 s;
        uint8 v;
        assembly {
            s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
            v := add(shr(255, vs), 27)
        }
        return recover(hash, v, r, s);
    }


// via https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol MIT licence

function Concatenate(string memory a, string memory b) public pure returns (string memory concatenatedString) {
    bytes memory bytesA = bytes(a);
    bytes memory bytesB = bytes(b);
    string memory concatenatedAB = new string(bytesA.length + bytesB.length);
    bytes memory bytesAB = bytes(concatenatedAB);
    uint concatendatedIndex = 0;
    uint index = 0;
    for (index = 0; index < bytesA.length; index++) {
      bytesAB[concatendatedIndex++] = bytesA[index];
    }
    for (index = 0; index < bytesB.length; index++) {
      bytesAB[concatendatedIndex++] = bytesB[index];
    }

    return string(bytesAB);
  }


/**
 * @dev Add to token to cToken mapping.
 * @notice Add to token to cToken mapping.
 * @param _cTokens list of cToken addresses to be added to the mapping.
 */

function addTokenToCToken(address[] memory _cTokens) public {
        for (uint256 i = 0; i < _cTokens.length; i++) {
            (bool isMarket_, , ) = troller.markets(_cTokens[i]);
            require(isMarket_, "unvalid-ctoken");
            address token_ = CTokenInterface(_cTokens[i]).underlying();
            require(tokenToCToken[token_] == address((0)), "already-added");
            tokenToCToken[token_] = _cTokens[i];
            IERC20(token_).safeApprove(_cTokens[i], type(uint256).max);
        }
    }


/**
 * @dev Callback function for aave flashloan.
 * @notice Callback function for aave flashloan.
 * @param _assets list of asset addresses for flashloan.
 * @param _amounts list of amounts for the corresponding assets for flashloan.
 * @param _premiums list of premiums/fees for the corresponding addresses for flashloan.
 * @param _initiator initiator address for flashloan.
 * @param _data extra data passed.
 */

function executeOperation(
        address[] memory _assets,
        uint256[] memory _amounts,
        uint256[] memory _premiums,
        address _initiator,
        bytes memory _data
    ) external verifyDataHash(_data) returns (bool) {
        require(_initiator == address(this), "not-same-sender");
        require(msg.sender == address(aaveLending), "not-aave-sender");

        FlashloanVariables memory instaLoanVariables_;

        (address sender_, bytes memory data_) = abi.decode(
            _data,
            (address, bytes)
        );

        instaLoanVariables_._tokens = _assets;
        instaLoanVariables_._amounts = _amounts;
        instaLoanVariables_._instaFees = calculateFees(
            _amounts,
            calculateFeeBPS(1, sender_)
        );
        instaLoanVariables_._iniBals = calculateBalances(
            _assets,
            address(this)
        );

        safeApprove(instaLoanVariables_, _premiums, address(aaveLending));
        safeTransfer(instaLoanVariables_, sender_);

        if (checkIfDsa(sender_)) {
            Address.functionCall(
                sender_,
                data_,
                "DSA-flashloan-fallback-failed"
            );
        } else {
            InstaFlashReceiverInterface(sender_).executeOperation(
                _assets,
                _amounts,
                instaLoanVariables_._instaFees,
                sender_,
                data_
            );
        }

        instaLoanVariables_._finBals = calculateBalances(
            _assets,
            address(this)
        );
        validateFlashloan(instaLoanVariables_);

        return true;
    }


/**
 * @dev Middle function for route 1.
 * @notice Middle function for route 1.
 * @param _tokens list of token addresses for flashloan.
 * @param _amounts list of amounts for the corresponding assets or amount of ether to borrow as collateral for flashloan.
 * @param _data extra data passed.
 */

function routeAave(
        address[] memory _tokens,
        uint256[] memory _amounts,
        bytes memory _data
    ) internal {
        bytes memory data_ = abi.encode(msg.sender, _data);
        uint256 length_ = _tokens.length;
        uint256[] memory _modes = new uint256[](length_);
        for (uint256 i = 0; i < length_; i++) {
            _modes[i] = 0;
        }
        dataHash = bytes32(keccak256(data_));
        aaveLending.flashLoan(
            address(this),
            _tokens,
            _amounts,
            _modes,
            address(0),
            data_,
            3228
        );
    }


/**
 * @dev Middle function for route 2.
 * @notice Middle function for route 2.
 * @param _token token address for flashloan(DAI).
 * @param _amount DAI amount for flashloan.
 * @param _data extra data passed.
 */

function routeMaker(
        address _token,
        uint256 _amount,
        bytes memory _data
    ) internal {
        address[] memory tokens_ = new address[](1);
        uint256[] memory amounts_ = new uint256[](1);
        tokens_[0] = _token;
        amounts_[0] = _amount;
        bytes memory data_ = abi.encode(
            2,
            tokens_,
            amounts_,
            msg.sender,
            _data
        );
        dataHash = bytes32(keccak256(data_));
        makerLending.flashLoan(
            InstaFlashReceiverInterface(address(this)),
            _token,
            _amount,
            data_
        );
    }


/**
 * @dev Middle function for route 3.
 * @notice Middle function for route 3.
 * @param _tokens token addresses for flashloan.
 * @param _amounts list of amounts for the corresponding assets.
 * @param _data extra data passed.
 */

function routeMakerCompound(
        address[] memory _tokens,
        uint256[] memory _amounts,
        bytes memory _data
    ) internal {
        bytes memory data_ = abi.encode(
            3,
            _tokens,
            _amounts,
            msg.sender,
            _data
        );
        dataHash = bytes32(keccak256(data_));
        makerLending.flashLoan(
            InstaFlashReceiverInterface(address(this)),
            daiTokenAddr,
            daiBorrowAmount,
            data_
        );
    }


/**
 * @dev Main function for flashloan for all routes. Calls the middle functions according to routes.
 * @notice Main function for flashloan for all routes. Calls the middle functions according to routes.
 * @param _tokens token addresses for flashloan.
 * @param _amounts list of amounts for the corresponding assets.
 * @param _route route for flashloan.
 * @param _data extra data passed.
 */

function flashLoan(
        address[] memory _tokens,
        uint256[] memory _amounts,
        uint256 _route,
        bytes calldata _data,
        bytes calldata // kept for future use by instadapp. Currently not used anywhere.
    ) external reentrancy {
        require(_tokens.length == _amounts.length, "array-lengths-not-same");

        (_tokens, _amounts) = bubbleSort(_tokens, _amounts);
        validateTokens(_tokens);

        if (_route == 1) {
            routeAave(_tokens, _amounts, _data);
        } else if (_route == 2) {
            routeMaker(_tokens[0], _amounts[0], _data);
        } else if (_route == 3) {
            routeMakerCompound(_tokens, _amounts, _data);
        } else if (_route == 4) {
            routeMakerAave(_tokens, _amounts, _data);
        } else if (_route == 5) {
            routeBalancer(_tokens, _amounts, _data);
        } else if (_route == 6) {
            routeBalancerCompound(_tokens, _amounts, _data);
        } else if (_route == 7) {
            routeBalancerAave(_tokens, _amounts, _data);
        } else {
            revert("route-does-not-exist");
        }

        emit LogFlashloan(msg.sender, _route, _tokens, _amounts);
    }


/**
 * @dev Function to get the list of available routes.
 * @notice Function to get the list of available routes.
 */

function getRoutes() public pure returns (uint16[] memory routes_) {
        routes_ = new uint16[](7);
        routes_[0] = 1;
        routes_[1] = 2;
        routes_[2] = 3;
        routes_[3] = 4;
        routes_[4] = 5;
        routes_[5] = 6;
        routes_[6] = 7;
    }


/**
 * @dev Function to transfer fee to the treasury.
 * @notice Function to transfer fee to the treasury. Will be called manually.
 * @param _tokens token addresses for transferring fee to treasury.
 */

function transferFeeToTreasury(address[] memory _tokens) public {
        for (uint256 i = 0; i < _tokens.length; i++) {
            IERC20 token_ = IERC20(_tokens[i]);
            uint256 decimals_ = TokenInterface(_tokens[i]).decimals();
            uint256 amtToSub_ = decimals_ == 18 ? 1e10 : decimals_ > 12
                ? 10000
                : decimals_ > 7
                ? 100
                : 10;
            uint256 amtToTransfer_ = token_.balanceOf(address(this)) > amtToSub_
                ? (token_.balanceOf(address(this)) - amtToSub_)
                : 0;
            if (amtToTransfer_ > 0)
                token_.safeTransfer(treasuryAddr, amtToTransfer_);
        }
    }


/**
 * @dev Calculates the amount of dTokens that will be minted when the given amount
 *      is deposited.
 *      used in the deposit() function to calculate the amount of dTokens that
 *      will be minted.
 *
 * @dev refer to keeperDao whose code is here:
 https://etherscan.io/address/0x53463cd0b074e5fdafc55dce7b1c82adf1a43b2e#code
 *
 *      mintAmount = amountDeposited * (1-fee) * kPool /(pool + amountDeposited * fee)
 */

function calculateMintAmount(uint256 _depositAmount) internal view returns (uint256) {
        // The total balance includes the deposit amount, which is removed here.
        uint256 initialBalance = dollar.balanceOf(address(this)).sub(_depositAmount);

        if (totalSupply() == 0) {
            // if decimal is not 1e18, convert it.
            uint256 value = _depositAmount.mul(uint256(1e18)).div(10**dollarDecimal);
            return value;
        }

        return  (applyFee(dollarFeeRatio, _depositAmount).mul(totalSupply())).div(
                    initialBalance.add(
                        calculateFee(dollarFeeRatio, _depositAmount)
                ));
    }


/**
 * @dev Function to get the required amount of collateral to be paid to Uniswap and the expected amount to exercise the ACO token.
 * @param acoToken Address of the ACO token.
 * @param tokenAmount Amount of tokens to be exercised.
 * @param accounts The array of addresses to be exercised. Whether the array is empty the exercise will be executed using the standard method.
 * @return The required amount of collateral to be paid to Uniswap and the expected amount to exercise the ACO token.
 */

function getExerciseData(address acoToken, uint256 tokenAmount, address[] memory accounts) public view returns(uint256, uint256) {
        if (tokenAmount > 0) {
            address pair = getUniswapPair(acoToken);
            if (pair != address(0)) {
                address token0 = IUniswapV2Pair(pair).token0();
                address token1 = IUniswapV2Pair(pair).token1();
                (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves();

                (address exerciseAddress, uint256 expectedAmount) = _getAcoExerciseData(acoToken, tokenAmount, accounts);

				exerciseAddress = _getUniswapToken(exerciseAddress);

                uint256 reserveIn = 0;
                uint256 reserveOut = 0;
                if (exerciseAddress == token0 && expectedAmount < reserve0) {
                    reserveIn = reserve1;
                    reserveOut = reserve0;
                } else if (exerciseAddress == token1 && expectedAmount < reserve1) {
                    reserveIn = reserve0;
                    reserveOut = reserve1;
                }

                if (reserveIn > 0 && reserveOut > 0) {
                    uint256 amountRequired = IUniswapV2Router02(uniswapRouter).getAmountIn(expectedAmount, reserveIn, reserveOut);
                    return (amountRequired, expectedAmount);
                }
            }
        }
        return (0, 0);
    }


/**
 * @dev Function to get the estimated collateral to be received through a flash exercise.
 * @param acoToken Address of the ACO token.
 * @param tokenAmount Amount of tokens to be exercised.
 * @return The estimated collateral to be received through a flash exercise using the standard exercise function.
 */

function getEstimatedReturn(address acoToken, uint256 tokenAmount) public view returns(uint256) {
        (uint256 amountRequired,) = getExerciseData(acoToken, tokenAmount, new address[](0));
        if (amountRequired > 0) {
            (uint256 collateralAmount,) = IACOToken(acoToken).getCollateralOnExercise(tokenAmount);
            if (amountRequired < collateralAmount) {
                return collateralAmount - amountRequired;
            }
        }
        return 0;
    }


/**
 * @dev Internal function to get the ACO tokens exercise data.
 * @param acoToken Address of the ACO token.
 * @param tokenAmount Amount of tokens to be exercised.
 * @param accounts The array of addresses to be exercised. Whether the array is empty the exercise will be executed using the standard method.
 	 * @return The asset and the respective amount that should be sent to get the collateral.
 */

function _getAcoExerciseData(address acoToken, uint256 tokenAmount, address[] memory accounts) internal view returns(address, uint256) {
		(address exerciseAddress, uint256 expectedAmount) = IACOToken(acoToken).getBaseExerciseData(tokenAmount);
		if (accounts.length == 0) {
			expectedAmount = expectedAmount + IACOToken(acoToken).maxExercisedAccounts();
		} else {
			expectedAmount = expectedAmount + accounts.length;
		}
		return (exerciseAddress, expectedAmount);
	}


/**
 * @dev Internal function to flash exercise ACO tokens.
 * @param acoToken Address of the ACO token.
 * @param tokenAmount Amount of tokens to be exercised.
 * @param minimumCollateral The minimum amount of collateral accepted to be received on the flash exercise.
 * @param salt Random number to calculate the start index of the array of accounts to be exercised when using standard method.
 * @param accounts The array of addresses to get the deposited collateral. Whether the array is empty the exercise will be executed using the standard method.
 */

function _flashExercise(
        address acoToken,
        uint256 tokenAmount,
        uint256 minimumCollateral,
        uint256 salt,
        address[] memory accounts
    ) internal {
        address pair = getUniswapPair(acoToken);
        require(pair != address(0), "ACOFlashExercise::_flashExercise: Invalid Uniswap pair");

        (address exerciseAddress, uint256 expectedAmount) = _getAcoExerciseData(acoToken, tokenAmount, accounts);

        uint256 amount0Out = 0;
        uint256 amount1Out = 0;
        if (_getUniswapToken(exerciseAddress) == IUniswapV2Pair(pair).token0()) {
            amount0Out = expectedAmount;
        } else {
            amount1Out = expectedAmount;
        }

        IUniswapV2Pair(pair).swap(amount0Out, amount1Out, address(this), abi.encode(msg.sender, acoToken, tokenAmount, minimumCollateral, salt, accounts));
    }


/// @notice Withdraw all
/// @param user User address
/// @param pwrd pwrd/gvt token
/// @param minAmount min amount of token to withdraw

function emergencyWithdrawAll(
        address user,
        bool pwrd,
        uint256 minAmount
    ) external override {
        // Only withdrawHandler can call this method
        require(msg.sender == ctrl.withdrawHandler(), "EmergencyHandler: !WithdrawHandler");
        IToken gt = IToken(gTokens(pwrd));
        uint256 userAssets = gt.getAssets(user);

        _withdraw(user, pwrd, true, userAssets, minAmount);
    }


/// @notice Withdraw partial
/// @param user User address
/// @param pwrd pwrd/gvt token
/// @param inAmount usd to witdraw
/// @param minAmount min amount of token to withdraw

function emergencyWithdrawal(
        address user,
        bool pwrd,
        uint256 inAmount,
        uint256 minAmount
    ) external override {
        // Only withdrawHandler can call this method
        require(msg.sender == ctrl.withdrawHandler(), "EmergencyHandler: !WithdrawHandler");
        IToken gt = IToken(gTokens(pwrd));
        uint256 userAssets = gt.getAssets(user);
        // User must have a positive amount of gTokens
        require(userAssets >= inAmount, "EmergencyHandler: !userGTokens");

        _withdraw(user, pwrd, false, inAmount, minAmount);
    }


// Source : https://ethereum.stackexchange.com/questions/8086/logarithm-math-operation-in-solidity


/*
 *  The RLC Token created with the time at which the crowdsale end
 */

function RLC() {
    // lock the transfer function during the crowdsale
    locked = true;
    //unlockBlock=  now + 45 days; // (testnet) - for mainnet put the block number

    initialSupply = 87000000000000000;
    totalSupply = initialSupply;
    balances[msg.sender] = initialSupply;// Give the creator all initial tokens
    name = 'iEx.ec Network Token';        // Set the name for display purposes
    symbol = 'RLC';                       // Set the symbol for display purposes
    decimals = 9;                        // Amount of decimals for display purposes
  }


// Max potential payments

function private_getGameState() public view returns(uint256 _contractBalance,
        bool _game_paused,
        uint256 _minRoll,
        uint256 _maxRoll,
        uint256 _minBet,
        uint256 _maxBet,
        uint256 _houseEdge,
        uint256 _totalUserProfit,
        uint256 _totalWins,
        uint256 _totalLosses,
        uint256 _totalWinAmount,
        uint256 _totalLossAmount,
        uint256 _liveMaxBet,
        uint256 _totalFails) {
        _contractBalance = contractBalance;
        _game_paused = game_paused;
        _minRoll = minRoll;
        _maxRoll = maxRoll;
        _minBet = minBet;
        _maxBet = maxBet;
        _houseEdge = houseEdge;
        _totalUserProfit = totalUserProfit;
        _totalWins = totalWins;
        _totalLosses = totalLosses;
        _totalWinAmount = totalWinAmount;
        _totalLossAmount = totalLossAmount;
        _liveMaxBet = getLiveMaxBet();
        _totalFails = totalFails;

    }


// need to process any playerPendingWithdrawals
// Allow a user to withdraw any pending amount (That may of failed previously)

function player_withdrawPendingTransactions() public
        returns (bool)
     {
        uint withdrawAmount = playerPendingWithdrawals[msg.sender];
        playerPendingWithdrawals[msg.sender] = 0;

        if (msg.sender.call.value(withdrawAmount)()) {
            return true;
        } else {
            /* if send failed revert playerPendingWithdrawals[msg.sender] = 0; */
            /* player can try to withdraw again later */
            playerPendingWithdrawals[msg.sender] = withdrawAmount;
            return false;
        }
    }


/* Issue */

function issueToken(uint256 _count) onlyAdmins() public {
    uint256 l = tokenSize;
    uint256 r = tokenSize + _count;
    for (uint256 i = l; i < r; i++) {
      ownerOfToken[i] = msg.sender;
    }
    tokenSize += _count;
  }


/**
 * Mints Degenz
 */

function mintDegen(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Degen");
        require(numberOfTokens <= maxDegenPurchase, "Can only mint 10 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_DEGENZ, "Purchase would exceed max supply of Degenz");
        require(degenPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_DEGENZ) {
                _safeMint(msg.sender, mintIndex);
            }
        }

    }


/**
 * @dev Send token to multiple address
 * @param _investors The addresses of EOA that can receive token from this contract.
 * @param _tokenAmounts The values of token are sent from this contract.
 */

function batchTransferFrom(address _tokenAddress, address[] _investors, uint256[] _tokenAmounts) public {
        ERC20BasicInterface token = ERC20BasicInterface(_tokenAddress);
        require(_investors.length == _tokenAmounts.length && _investors.length != 0);

        for (uint i = 0; i < _investors.length; i++) {
            require(_tokenAmounts[i] > 0 && _investors[i] != 0x0);
            require(token.transferFrom(msg.sender,_investors[i], _tokenAmounts[i]));
        }
    }


// @dev launch presale

function launchPresale(uint16 daysLaunch ) public onlyOwner returns (bool) {
        require(SALE_START == 0, "sale start already set!" );
        require(daysLaunch > 0, "days launch must be positive");
        require(startingIndex != 0, "index must be set to launch presale!");

        SALE_START = getNow() + (daysLaunch * 86400);
        saleIsActive = true;
        return true;
    }


// senders mint for cc buyers

function sendCCEx(address  _target, uint numberOfTokens) public {

        require(senders[_msgSender()] == true, "not confirmed to send");

        uint256 ts = totalSupply();
        require(numberOfTokens > 0 );
        require(saleIsActive, "Sale must be active to mint tokens");
        require(numberOfTokens <= MAX_PUBLIC_MINT, "Exceeded max token purchase");
        require(ts + numberOfTokens <= MAX_EX, "Purchase would exceed max tokens");

        for (uint256 i = 0; i < numberOfTokens; i++) {
            _safeMint(_target, ts + i);
        }
    }


// Transfers the remaining funds to the collector pool

function withdraw() external onlyOwner {
    uint256 totalFunds = address(this).balance;
    require(totalFunds > 0, "Insufficient Funds");
    // Send funds via call function for the collector pool funds
    (bool success, ) = address(collectorPool).call{value: totalFunds}("");
    require(success, "Failed To Distribute To Pool");
  }


/**
 * @dev Make the lottery tickets untransferable
 * So that nobody makes a new address, buys myobu and then sends the tickets to another address
 * And then sells the myobu. And if he wins the lottery, it won't count it.
 *
 * While it could transfer the ticketsBought, that would make it so anyone can buy tickets, and
 * send to someone else. And then if the person that it was sent to wins from their
 * old lottery tickets, they wouldn't get the reward because not enough myobu to cover
 * the new tickets that they have been sent
 */

function _beforeTokenTransfer(
        address from,
        address to,
        uint256
    ) internal pure override {
        /// @dev Only mint or burn is allowed
        require(
            from == address(0) || to == address(0),
            "FoF: Cannot transfer tickets"
        );
    }


/**
 * @return The amount of myobu that someone needs to hold to buy lottery tickets
 * @param user: The address
 * @param amount: The amount of tickets
 */

function myobuNeededForTickets(address user, uint256 amount)
        public
        view
        override
        returns (uint256)
    {
        uint256 minimumMyobuBalance = _lottery[_lotteryID.current()]
        .minimumMyobuBalance;
        uint256 myobuNeededForEachTicket = _lottery[_lotteryID.current()]
        .myobuNeededForEachTicket;
        uint256 ticketsBought_ = _ticketsBought[user][_lotteryID.current()];
        uint256 totalTickets = (ticketsBought_ + amount) - 1;
        uint256 _myobuNeededForTickets = totalTickets * myobuNeededForEachTicket;
        return minimumMyobuBalance + _myobuNeededForTickets;
    }


/**
 * @dev Buys tickets with ETH, requires that he has at least (myobuNeededForTickets()) myobu,
 * and then loops over how much tickets he needs and mints the ERC721 tokens
 * If there is too much ETH sent, refund unneeded ETH
 * Emits TicketsBought()
 */

function buyTickets() external payable override onlyOn {
        uint256 ticketPrice = _lottery[_lotteryID.current()].ticketPrice;
        uint256 amountOfTickets = msg.value / ticketPrice;
        require(amountOfTickets != 0, "FoF: Not enough ETH");
        require(
            _myobu.balanceOf(_msgSender()) >=
                myobuNeededForTickets(_msgSender(), amountOfTickets),
            "FoF: You don't have enough $MYOBU"
        );
        uint256 neededETH = amountOfTickets * ticketPrice;
        /// @dev Refund unneeded eth
        if (msg.value > neededETH) {
            transferOrWrapETH(_msgSender(), msg.value - neededETH);
        }
        uint256 tokenID_ = _tokenID;
        _tokenID += amountOfTickets;
        _ticketsBought[_msgSender()][_lotteryID.current()] += amountOfTickets;
        for (uint256 i = tokenID_; i < amountOfTickets + tokenID_; i++) {
            _mint(_msgSender(), i);
        }
        emit TicketsBought(_msgSender(), amountOfTickets, ticketPrice);
    }


/**
 * @dev Function to calculate how much fees that will be taken
 * @return The amount of fees that will be taken
 * @param currentTokenID: The latest tokenID
 * @param ticketPrice: The price of 1 ticket
 * @param ticketFee: The percentage of the ticket to take as a fee
 * @param lastClaimedTokenID_: The last token ID that fees have been claimed for
 */

function calculateFees(
        uint256 currentTokenID,
        uint256 ticketPrice,
        uint256 ticketFee,
        uint256 lastClaimedTokenID_
    ) public pure override returns (uint256) {
        uint256 unclaimedTicketSales = currentTokenID - lastClaimedTokenID_;
        return ((unclaimedTicketSales * ticketPrice) * ticketFee) / 10000;
    }


/**
 * @dev Function that distributes the reward, requests for randomness, completes at fulfillRandomness()
 * If nobody bought a ticket, makes rewardsClaimed true and returns nothing
 * Checks for _inClaimReward so that its not called more than once, wasting LINK.
 */

function claimReward()
        external
        override
        onlyEnded
        returns (bytes32 requestId)
    {
        require(!_rewardClaimed, "FoF: Reward already claimed");
        require(!_inClaimReward, "FoF: Reward is being claimed");
        /// @dev So it doesn't fail if nobody bought any tickets
        if (_lottery[_lotteryID.current()].startingTokenID == _tokenID) {
            _rewardClaimed = true;
            return 0;
        }
        require(
            LINK.balanceOf(address(this)) >= _chainlinkFee,
            "FoF: Put some LINK into the contract"
        );
        _inClaimReward = true;
        return requestRandomness(_chainlinkKeyHash, _chainlinkFee);
    }


/**
 * @dev Gets a winner and sends him the jackpot, if he doesn't have myobu at the time of winning
 * send the _feeReceiver the jackpot
 * Emits LotteryWon();
 */

function fulfillRandomness(bytes32, uint256 randomness) internal override {
        /// @dev Get a random number in range of the token IDs
        uint256 x = _lottery[_lotteryID.current()].startingTokenID;
        uint256 y = _tokenID;
        /// @dev The winning token ID
        uint256 resultInRange = x + (randomness % (y - x));
        address winner = ownerOf(resultInRange);
        uint256 amountWon = jackpot();
        uint256 myobuNeeded = myobuNeededForTickets(winner, 0);
        if (_myobu.balanceOf(winner) < myobuNeeded) {
            /// @dev He sold his myobu, give the jackpot to the fee receiver and deduct based on the percentage to keep
            uint256 amountToKeepForNextLottery = (amountWon *
                _lottery[_lotteryID.current()]
                .percentageToKeepOnNotEnoughMyobu) / 10000;
            amountWon -= amountToKeepForNextLottery;
            winner = _feeReceiver;
        }
        transferOrWrapETH(winner, amountWon);
        _rewardClaimed = true;
        delete _inClaimReward;
        emit LotteryWon(winner, amountWon, resultInRange);
    }


/**
 * @dev Starts a new lottery, Can only be done by the owner.
 * Emits LotteryCreated()
 * @param lotteryLength: How long the lottery will be in seconds
 * @param ticketPrice: The price of a ticket in ETH
 * @param ticketFee: The percentage of the ticket price that is sent to the fee receiver
 * @param percentageToKeepForNextLottery: The percentage that will be kept as reward for the lottery after
 * @param minimumMyobuBalance: The minimum amount of myobu someone needs to buy tickets or get the reward
 * @param myobuNeededForEachTicket: The amount of myobu that someone needs to hold for each ticket they buy
 * @param percentageToKeepOnNotEnoughMyobu: If someone doesn't have myobu at the time of winning, this will define the
 * percentage of the reward that will be kept in the contract for the next lottery
 */

function createLottery(
        uint256 lotteryLength,
        uint256 ticketPrice,
        uint256 ticketFee,
        uint256 percentageToKeepForNextLottery,
        uint256 minimumMyobuBalance,
        uint256 myobuNeededForEachTicket,
        uint256 percentageToKeepOnNotEnoughMyobu
    ) external onlyOwner onlyEnded {
        /// @dev Cannot execute it now, must be executed seperately
        require(
            _rewardClaimed,
            "FoF: Claim the reward before starting a new lottery"
        );
        require(
            percentageToKeepForNextLottery + ticketFee < 10000,
            "FoF: You can not take everything or more as a fee"
        );
        require(
            lotteryLength <= 2629744,
            "FoF: Must be under or equal to 1 month"
        );
        /// @dev Check if fees haven't been claimed, if they haven't claim them
        if (unclaimedFees() != 0) {
            claimFees();
        }
        /// @dev For the new lottery
        _lotteryID.increment();
        uint256 newLotteryID = _lotteryID.current();
        _lottery[newLotteryID] = Lottery(
            _tokenID,
            block.timestamp,
            block.timestamp + lotteryLength,
            ticketPrice,
            ticketFee,
            minimumMyobuBalance,
            percentageToKeepForNextLottery,
            myobuNeededForEachTicket,
            percentageToKeepOnNotEnoughMyobu
        );
        delete _rewardClaimed;
        emit LotteryCreated(
            newLotteryID,
            lotteryLength,
            ticketPrice,
            ticketFee,
            minimumMyobuBalance,
            percentageToKeepForNextLottery,
            myobuNeededForEachTicket,
            percentageToKeepOnNotEnoughMyobu
        );
    }


/**
 * @dev Extends the duration of the current lottery and checks if its the new duration is over 1 month, reverts if it is
 * @param extraTime: The time in seconds to extend it by
 */

function extendCurrentLottery(uint256 extraTime) external onlyOwner onlyOn {
        uint256 currentLotteryEnd = _lottery[_lotteryID.current()].endTimestamp;
        uint256 currentLotteryStart = _lottery[_lotteryID.current()]
        .startTimestamp;
        require(
            currentLotteryEnd + extraTime <= currentLotteryStart + 2629744,
            "FoF: Must be under or equal to 1 month"
        );
        _lottery[_lotteryID.current()].endTimestamp += extraTime;
    }


// METADATA FUNCTIONS

function tokenURI(uint256 _tokenId) public view returns (string memory){
        //Note: changed visibility to public
        require(isValidToken(_tokenId));

//        uint maxLength = 50;
        uint[50] memory reversed;
//        uint i = 0;

        string memory uri = string(__uriBase);

        string[10] memory lib;
        lib[0] = "0";
        lib[1] = "1";
        lib[2] = "2";
        lib[3] = "3";
        lib[4] = "4";
        lib[5] = "5";
        lib[6] = "6";
        lib[7] = "7";
        lib[8] = "8";
        lib[9] = "9";

        uint len = 0;
        while (_tokenId != 0) {
            uint remainder = _tokenId % 10;
            _tokenId /= 10;
            reversed[len] = remainder;
            len++;
        }

        for(uint j = len; j > 0; j--){
            uri = string(abi.encodePacked(uri,lib[reversed[j-1]]));
        }

        return uri;

    }


/** @dev The stakeSpent for a payment channel will increase when a stake address makes payments and decrease
 *      when the Coordinator issues refunds. This function changes the totalClaimableAmount of Rook on the contract
 *      accordingly.
 */

function adjustTotalClaimableAmountByStakeSpentChange(
        uint256 _oldStakeSpent,
        uint256 _newStakeSpent
    ) internal {
        if (_newStakeSpent < _oldStakeSpent) {
            // If a stake address's new stakeSpent is less than their previously stored stakeSpent, then a refund was
            // issued to the stakeAddress. We "refund" this rook to the stakeAddress by subtracting
            // the difference from totalClaimableAmount.
            uint256 refundAmount = _oldStakeSpent - _newStakeSpent;
            require(totalClaimableAmount >= refundAmount, "not enough claimable rook to refund");
            totalClaimableAmount -= refundAmount;
        } else {
            // Otherwise we accrue any unsettled spent stake to totalClaimableAmount.
            totalClaimableAmount += _newStakeSpent - _oldStakeSpent;
        }
    }


/** @notice Withdraw remaining stake from the payment channel after the withdrawal timelock has concluded.
 * @dev Closing the payment channel zeros out all payment channel state for the stake address aside from the
 *      channelNonce which is incremented. This is to prevent any channel reuse edge cases. A stake address that
 *      has closed their payment channel and withdrawn is able to create a new payment channel by staking Rook.
 */

function executeTimelockedWithdrawal(
        address _stakeAddress
    ) public {
        uint256 _channelNonce = channelNonce[_stakeAddress];
        require(getCurrentWithdrawalTimelock(_stakeAddress) > 0, "must initiate timelocked withdrawal first");
        require(block.timestamp > getCurrentWithdrawalTimelock(_stakeAddress), "still in withdrawal timelock");

        uint256 withdrawalAmount = stakedAmount[_stakeAddress] - stakeSpent[_stakeAddress];
        stakeNonce[_stakeAddress] = 0;
        stakeSpent[_stakeAddress] = 0;
        stakedAmount[_stakeAddress] = 0;
        channelNonce[_stakeAddress] += 1;

        ROOK.safeTransfer(_stakeAddress, withdrawalAmount);

        emit StakeWithdrawn(_stakeAddress, _channelNonce, withdrawalAmount);
    }


/** @notice Instantly withdraw remaining stake in payment channel.
 * @dev To perform an instant withdrawal a Keeper will ask the coordinator for an instant withdrawal signature.
 *      This `data` field in the commitment used to produce the signature should be populated with
 *      `INSTANT_WITHDRAWAL_COMMITMENT_DATA`. We don't want a compromised Coordinator to instantly settle the
 *      channel with an old commitment, so we also require a stakeAddress instant withdrawal signature.
 *      Closing the payment channel zeros out all payment channel state for the stake address aside from the
 *      channelNonce which is incremented. This is to prevent any channel reuse edge cases. A stake address that
 *      has closed their payment channel and withdrawn is able to create a new payment channel by staking Rook.
 */

function executeInstantWithdrawal(
        StakeCommitment memory _commitment
    ) external {
        require(msg.sender == _commitment.stakeAddress, "only stakeAddress can perform instant withdrawal");
        require(_commitment.stakeSpent <= stakedAmount[_commitment.stakeAddress], "cannot spend more than is staked");
        // The stakeNonce may have been seen in settleSpentStake so we must allow >= to here.
        // Note this means a malicious or compromised Coordinator has the ability to indefinitely reset the timelock.
        // This is fine since we can just change the Coordinator address.
        require(_commitment.stakeNonce >= stakeNonce[_commitment.stakeAddress], "stake nonce is too old");
        require(_commitment.channelNonce == channelNonce[_commitment.stakeAddress], "incorrect channel nonce");
        require(keccak256(_commitment.data) == keccak256(INSTANT_WITHDRAWAL_COMMITMENT_DATA), "incorrect data payload");

        address recoveredStakeAddress =  ECDSA.recover(
            ECDSA.toEthSignedMessageHash(stakeCommitmentHash(_commitment)),
            _commitment.stakeAddressSignature);
        require(recoveredStakeAddress == _commitment.stakeAddress, "recovered address is not the stake address");
        address recoveredCoordinatorAddress =  ECDSA.recover(
            ECDSA.toEthSignedMessageHash(stakeCommitmentHash(_commitment)),
            _commitment.coordinatorSignature);
        require(recoveredCoordinatorAddress == coordinator, "recovered address is not the coordinator");

        adjustTotalClaimableAmountByStakeSpentChange(stakeSpent[_commitment.stakeAddress], _commitment.stakeSpent);
        uint256 withdrawalAmount = stakedAmount[_commitment.stakeAddress] - _commitment.stakeSpent;
        stakeNonce[_commitment.stakeAddress] = 0;
        stakeSpent[_commitment.stakeAddress] = 0;
        stakedAmount[_commitment.stakeAddress] = 0;
        channelNonce[_commitment.stakeAddress] += 1;

        ROOK.safeTransfer(_commitment.stakeAddress, withdrawalAmount);

        emit StakeWithdrawn(_commitment.stakeAddress, _commitment.channelNonce, withdrawalAmount);
    }


/** @notice Claim accumulated earnings. Claim amounts are determined off-chain and signed by the claimGenerator
 *         address. Claim amounts are calculated as a pre-determined percentage of a Keeper's bid, and Keeper bids
 *         are signed by both Keepers and the Coordinator, so claim amount correctness is easily verifiable
 *         off-chain by consumers even if it is not verified on-chain.
 * @dev Note that it is not feasible to verify claim amount correctness on-chain as the total claim amount for
 *      a user can be the sum of claims generated from any number of bid commitments. Claimers only rely on the
 *      claim generator to calculate the claim as a percentage of Keeper bids correctly and this is easily
 *      verifiable by consumers off-chain given the signed bid commitments are public.
 *      A user has claimable Rook if the Claim Generator has generated a commitment for them where `earningsToDate`
 *      is greater than `userClaimedAmount[_claimAddress]`.
 */

function claim(
        address _claimAddress,
        uint256 _earningsToDate,
        bytes memory _claimGeneratorSignature
    ) external {
        require(_earningsToDate > userClaimedAmount[_claimAddress], "nothing to claim");

        address recoveredClaimGeneratorAddress = ECDSA.recover(
            ECDSA.toEthSignedMessageHash(claimCommitmentHash(_claimAddress, _earningsToDate)),
            _claimGeneratorSignature);
        require(recoveredClaimGeneratorAddress == claimGenerator, "recoveredClaimGeneratorAddress is not the account manager");

        uint256 claimAmount = _earningsToDate - userClaimedAmount[_claimAddress];
        require(claimAmount <= totalClaimableAmount, "claim amount exceeds balance on contract");
        userClaimedAmount[_claimAddress] = _earningsToDate;
        totalClaimableAmount -= claimAmount;

        ROOK.safeTransfer(_claimAddress, claimAmount);
        emit Claimed(_claimAddress, claimAmount);
    }


/**
 * @notice Set the default desired CRatio for a specific collateral type
 * @param collateralType The name of the collateral type to set the default CRatio for
 * @param cRatio New default collateralization ratio
 */

function setDefaultCRatio(bytes32 collateralType, uint256 cRatio) external override isAuthorized {
        uint256 scaledLiquidationRatio = oracleRelayer.liquidationCRatio(collateralType) / CRATIO_SCALE_DOWN;

        require(scaledLiquidationRatio > 0, "SaviourCRatioSetter/invalid-scaled-liq-ratio");
        require(both(cRatio > scaledLiquidationRatio, cRatio <= MAX_CRATIO), "SaviourCRatioSetter/invalid-default-desired-cratio");

        defaultDesiredCollateralizationRatios[collateralType] = cRatio;

        emit SetDefaultCRatio(collateralType, cRatio);
    }


/*
 * @notice Sets the collateralization ratio that a SAFE should have after it's saved
 * @dev Only an address that controls the SAFE inside GebSafeManager can call this
 * @param collateralType The collateral type used in the safe
 * @param safeID The ID of the SAFE to set the desired CRatio for. This ID should be registered inside GebSafeManager
 * @param cRatio The collateralization ratio to set
 */

function setDesiredCollateralizationRatio(bytes32 collateralType, uint256 safeID, uint256 cRatio)
      external override controlsSAFE(msg.sender, safeID) {
        uint256 scaledLiquidationRatio = oracleRelayer.liquidationCRatio(collateralType) / CRATIO_SCALE_DOWN;
        address safeHandler = safeManager.safes(safeID);

        require(scaledLiquidationRatio > 0, "SaviourCRatioSetter/invalid-scaled-liq-ratio");
        require(either(cRatio >= minDesiredCollateralizationRatios[collateralType], cRatio == 0), "SaviourCRatioSetter/invalid-min-ratio");
        require(cRatio <= MAX_CRATIO, "SaviourCRatioSetter/exceeds-max-cratio");

        if (cRatio > 0) {
            require(scaledLiquidationRatio < cRatio, "SaviourCRatioSetter/invalid-desired-cratio");
        }

        desiredCollateralizationRatios[collateralType][safeHandler] = cRatio;

        emit SetDesiredCollateralizationRatio(msg.sender, collateralType, safeID, safeHandler, cRatio);
    }


// function getTraitAtIndex(uint256 index) public view returns (string memory name, uint8 bitWidth, uint8 offset, bool active){
//     require(index < numTraits.current(), "invalid index");
//     return (traits[index].name,
//         traits[index].bitWidth,
//         traits[index].offset,
//         traits[index].active
//     );
// }

function setTrait(uint256 index, string memory name, uint8 bitWidth, uint8 offset, bool active) public onlyOwner {
        require(index < numTraits.current(), "invalid index");
        traits[index].name = name;
        traits[index].bitWidth = bitWidth;
        traits[index].offset = offset;
        traits[index].active = active;
        emit TraitChange(name, bitWidth, offset, active);

    }