// @notice tokensale
// @param recipient The address of the recipient
// @return the transaction address and send the event as Transfer

function tokensale(address recipient) public payable {
        require(recipient != 0x0);

        uint256 weiAmount = msg.value;
        uint tokens = weiAmount.mul(getPrice());

        require(_leftSupply >= tokens);

        balances[owner] = balances[owner].sub(tokens);
        balances[recipient] = balances[recipient].add(tokens);

        _leftSupply = _leftSupply.sub(tokens);

        TokenPurchase(msg.sender, recipient, weiAmount, tokens);
    }


// Token distribution to founder, develoment team, partners, charity, and bounty

function sendBPESOToken(address to, uint256 value) public onlyOwner {
        require (
            to != 0x0 && value > 0 && _leftSupply >= value
        );

        balances[owner] = balances[owner].sub(value);
        balances[to] = balances[to].add(value);
        _leftSupply = _leftSupply.sub(value);
        Transfer(owner, to, value);
    }


// @notice send `value` token to `to` from `from`
// @param from The address of the sender
// @param to The address of the recipient
// @param value The amount of token to be transferred
// @return the transaction address and send the event as Transfer

function transferFrom(address from, address to, uint256 value) public {
        require (
            allowed[from][msg.sender] >= value && balances[from] >= value && value > 0
        );
        balances[from] = balances[from].sub(value);
        balances[to] = balances[to].add(value);
        allowed[from][msg.sender] = allowed[from][msg.sender].sub(value);
        Transfer(from, to, value);
    }


/**
 * @dev Joins and plays game.
 * @param _id Game id to join.
 * @param _referral Address for referral.
 * TESTED
 */

function joinAndPlayGame(uint256 _id, address _referral) external payable onlyNotCreator(_id) onlyAllowedToPlay {
    Game storage game = games[_id];
    require(game.creator != address(0), "No game with such id");
    require(game.winner == address(0), "Game has winner");
    require(game.bet == msg.value, "Wrong bet");
    require(_referral != msg.sender, "Wrong referral");

    addressBetTotal[msg.sender] = addressBetTotal[msg.sender].add(msg.value);

    game.opponent = msg.sender;
    if (_referral != address(0)) {
      game.opponentReferral = _referral;
    }

    //  play
    uint8 coinSide = uint8(uint256(keccak256(abi.encodePacked(now, msg.sender, gamesCreatedAmount, totalUsedInGame,devFeePending))) %2);
    game.winner = (coinSide == game.creatorGuessCoinSide) ? game.creator : game.opponent;

    gamesWithPendingPrizeWithdrawalForAddress[game.winner].push(_id);

    raffleParticipants.push(game.creator);
    raffleParticipants.push(game.opponent);
    lastPlayTimestamp[msg.sender] = now;
    gamesCompletedAmount = gamesCompletedAmount.add(1);
    totalUsedInGame = totalUsedInGame.add(msg.value);
    participatedGameIdxsForPlayer[msg.sender].push(_id);
    delete ongoingGameIdxForCreator[game.creator];

    if (isTopGame(_id)) {
      removeTopGame(game.id);
    }

    emit CF_GamePlayed(_id, game.creator, game.opponent, game.winner, game.bet);
  }


/**
 * @dev Withdraws prize for won game.
 * @param _maxLoop max loop.
 * TESTED
 */

function withdrawGamePrizes(uint256 _maxLoop) external {
    require(_maxLoop > 0, "_maxLoop == 0");

    uint256[] storage pendingGames = gamesWithPendingPrizeWithdrawalForAddress[msg.sender];
    require(pendingGames.length > 0, "no pending");
    require(_maxLoop <= pendingGames.length, "wrong _maxLoop");

    uint256 prizeTotal;
    for (uint256 i = 0; i < _maxLoop; i ++) {
      uint256 gameId = pendingGames[pendingGames.length.sub(1)];
      Game storage game = games[gameId];

      uint256 gamePrize = game.bet.mul(2);

      //  referral
      address winnerReferral = (msg.sender == game.creator) ? game.creatorReferral : game.opponentReferral;
      if (winnerReferral == address(0)) {
        winnerReferral = owner();
      }
      uint256 referralFee = gamePrize.mul(FEE_PERCENT).div(100);
      referralFeesPending[winnerReferral] = referralFeesPending[winnerReferral].add(referralFee);
      totalUsedReferralFees = totalUsedReferralFees.add(referralFee);

      prizeTotal += gamePrize;
      pendingGames.pop();
    }

    addressPrizeTotal[msg.sender] = addressPrizeTotal[msg.sender].add(prizeTotal);

    uint256 singleFee = prizeTotal.mul(FEE_PERCENT).div(100);
    partnerFeePending = partnerFeePending.add(singleFee);
    ongoinRafflePrize = ongoinRafflePrize.add(singleFee);
    devFeePending = devFeePending.add(singleFee.mul(2));

    prizeTotal = prizeTotal.sub(singleFee.mul(5));
    msg.sender.transfer(prizeTotal);

    //  partner transfer
    transferPartnerFee();

    emit CF_GamePrizesWithdrawn(msg.sender);
  }


/**
 * GameRaffle
 * TESTED
 */

function withdrawRafflePrizes() external override {
    require(rafflePrizePendingForAddress[msg.sender] > 0, "No raffle prize");

    uint256 prize = rafflePrizePendingForAddress[msg.sender];
    delete rafflePrizePendingForAddress[msg.sender];

    addressPrizeTotal[msg.sender] = addressPrizeTotal[msg.sender].add(prize);

    uint256 singleFee = prize.mul(FEE_PERCENT).div(100);
    partnerFeePending = partnerFeePending.add(singleFee);
    devFeePending = devFeePending.add(singleFee.mul(2));

    //  transfer prize
    prize = prize.sub(singleFee.mul(3));
    msg.sender.transfer(prize);

    //  partner transfer
    transferPartnerFee();

    emit CF_RafflePrizeWithdrawn(msg.sender, prize);
  }


/**
 * @dev Adds game idx to the beginning of topGames.
 * @param _id Game idx to be added.
 * TESTED
 */

function addTopGame(uint256 _id) external payable onlyCreator(_id) {
    require(msg.value == minBet, "Wrong fee");
    require(topGames[0] != _id, "Top in TopGames");

    uint256[5] memory topGamesTmp = [_id, 0, 0, 0, 0];
    bool isIdPresent;
    for (uint8 i = 0; i < 4; i ++) {
      if (topGames[i] == _id && !isIdPresent) {
        isIdPresent = true;
      }
      topGamesTmp[i+1] = (isIdPresent) ? topGames[i+1] : topGames[i];
    }
    topGames = topGamesTmp;
    devFeePending = devFeePending.add(msg.value);
    totalUsedInGame = totalUsedInGame.add(msg.value);

    emit CF_GameAddedToTop(_id, msg.sender);
  }


/**
 * @dev Removes game idx from topGames.
 * @param _id Game idx to be removed.
 * TESTED
 */

function removeTopGame(uint256 _id) private {
    uint256[5] memory tmpArr;
    bool found;

    for(uint256 i = 0; i < 5; i ++) {
      if(topGames[i] == _id) {
        found = true;
      } else {
        if (found) {
          tmpArr[i-1] = topGames[i];
        } else {
          tmpArr[i] = topGames[i];
        }
      }
    }

    require(found, "Not TopGame");
    topGames = tmpArr;
  }


/// @notice give the list of owners for the list of ids given.
/// @param ids The list if token ids to check.
/// @return addresses The list of addresses, corresponding to the list of ids.

function owners(uint256[] calldata ids) external view returns (address[] memory addresses) {
        addresses = new address[](ids.length);
        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            addresses[i] = address(uint160(_owners[id]));
        }
    }


/// @notice mint one of bleep if not already minted. Can only be called by `minter`.
/// @param id bleep id which represent a pair of (note, instrument).
/// @param to address that will receive the Bleep.

function mint(uint16 id, address to) external {
        require(msg.sender == minter, "ONLY_MINTER_ALLOWED");
        require(id < 1024, "INVALID_BLEEP");

        require(to != address(0), "NOT_TO_ZEROADDRESS");
        require(to != address(this), "NOT_TO_THIS");
        address owner = _ownerOf(id);
        require(owner == address(0), "ALREADY_CREATED");
        _safeTransferFrom(address(0), to, id, "");
    }


/// @notice mint multiple bleeps if not already minted. Can only be called by `minter`.
/// @param ids list of bleep id which represent each a pair of (note, instrument).
/// @param to address that will receive the Bleeps.

function multiMint(uint16[] calldata ids, address to) external {
        require(msg.sender == minter, "ONLY_MINTER_ALLOWED");
        require(to != address(0), "NOT_TO_ZEROADDRESS");
        require(to != address(this), "NOT_TO_THIS");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            require(id < 1024, "INVALID_BLEEP");
            address owner = _ownerOf(id);
            require(owner == address(0), "ALREADY_CREATED");
            _safeTransferFrom(address(0), to, id, "");
        }
    }


//management functions
//add lenders for the strategy to choose between
// only governance to stop strategist adding dodgy lender

function addLender(address a) public onlyGovernance {
        IGenericLender n = IGenericLender(a);
        require(n.strategy() == address(this), "Undocked Lender");

        for (uint256 i = 0; i < lenders.length; i++) {
            require(a != address(lenders[i]), "Already Added");
        }
        lenders.push(n);
    }


//force removes the lender even if it still has a balance

function _removeLender(address a, bool force) internal {
        for (uint256 i = 0; i < lenders.length; i++) {
            if (a == address(lenders[i])) {
                bool allWithdrawn = lenders[i].withdrawAll();

                if (!force) {
                    require(allWithdrawn, "WITHDRAW FAILED");
                }

                //put the last index here
                //remove last index
                if (i != lenders.length - 1) {
                    lenders[i] = lenders[lenders.length - 1];
                }

                //pop shortens array by 1 thereby deleting the last index
                lenders.pop();

                //if balance to spend we might as well put it into the best lender
                if (want.balanceOf(address(this)) > 0) {
                    adjustPosition(0);
                }
                return;
            }
        }
        require(false, "NOT LENDER");
    }


//Returns the status of all lenders attached the strategy

function lendStatuses() public view returns (lendStatus[] memory) {
        lendStatus[] memory statuses = new lendStatus[](lenders.length);
        for (uint256 i = 0; i < lenders.length; i++) {
            lendStatus memory s;
            s.name = lenders[i].lenderName();
            s.add = address(lenders[i]);
            s.assets = lenders[i].nav();
            s.rate = lenders[i].apr();
            statuses[i] = s;
        }

        return statuses;
    }


//the weighted apr of all lenders. sum(nav * apr)/totalNav

function estimatedAPR() public view returns (uint256) {
        uint256 bal = estimatedTotalAssets();
        if (bal == 0) {
            return 0;
        }

        uint256 weightedAPR = 0;

        for (uint256 i = 0; i < lenders.length; i++) {
            weightedAPR = weightedAPR.add(lenders[i].weightedApr());
        }

        return weightedAPR.div(bal);
    }


//Estimates the impact on APR if we add more money. It does not take into account adjusting position

function _estimateDebtLimitIncrease(uint256 change) internal view returns (uint256) {
        uint256 highestAPR = 0;
        uint256 aprChoice = 0;
        uint256 assets = 0;

        for (uint256 i = 0; i < lenders.length; i++) {
            uint256 apr = lenders[i].aprAfterDeposit(change);
            if (apr > highestAPR) {
                aprChoice = i;
                highestAPR = apr;
                assets = lenders[i].nav();
            }
        }

        uint256 weightedAPR = highestAPR.mul(assets.add(change));

        for (uint256 i = 0; i < lenders.length; i++) {
            if (i != aprChoice) {
                weightedAPR = weightedAPR.add(lenders[i].weightedApr());
            }
        }

        uint256 bal = estimatedTotalAssets().add(change);

        return weightedAPR.div(bal);
    }


//Estimates debt limit decrease. It is not accurate and should only be used for very broad decision making

function _estimateDebtLimitDecrease(uint256 change) internal view returns (uint256) {
        uint256 lowestApr = uint256(-1);
        uint256 aprChoice = 0;

        for (uint256 i = 0; i < lenders.length; i++) {
            uint256 apr = lenders[i].aprAfterDeposit(change);
            if (apr < lowestApr) {
                aprChoice = i;
                lowestApr = apr;
            }
        }

        uint256 weightedAPR = 0;

        for (uint256 i = 0; i < lenders.length; i++) {
            if (i != aprChoice) {
                weightedAPR = weightedAPR.add(lenders[i].weightedApr());
            } else {
                uint256 asset = lenders[i].nav();
                if (asset < change) {
                    //simplistic. not accurate
                    change = asset;
                }
                weightedAPR = weightedAPR.add(lowestApr.mul(change));
            }
        }
        uint256 bal = estimatedTotalAssets().add(change);
        return weightedAPR.div(bal);
    }


//gives estiomate of future APR with a change of debt limit. Useful for governance to decide debt limits

function estimatedFutureAPR(uint256 newDebtLimit) public view returns (uint256) {
        uint256 oldDebtLimit = vault.strategies(address(this)).totalDebt;
        uint256 change;
        if (oldDebtLimit < newDebtLimit) {
            change = newDebtLimit - oldDebtLimit;
            return _estimateDebtLimitIncrease(change);
        } else {
            change = oldDebtLimit - newDebtLimit;
            return _estimateDebtLimitDecrease(change);
        }
    }


/*
 * Key logic.
 *   The algorithm moves assets from lowest return to highest
 *   like a very slow idiots bubble sort
 *   we ignore debt outstanding for an easy life
 */

function adjustPosition(uint256 _debtOutstanding) internal override {
        _debtOutstanding; //ignored. we handle it in prepare return
        //emergency exit is dealt with at beginning of harvest
        if (emergencyExit) {
            return;
        }

        //we just keep all money in want if we dont have any lenders
        if (lenders.length == 0) {
            return;
        }

        (uint256 lowest, uint256 lowestApr, uint256 highest, uint256 potential) = estimateAdjustPosition();

        if (potential > lowestApr) {
            //apr should go down after deposit so wont be withdrawing from self
            lenders[lowest].withdrawAll();
        }

        uint256 bal = want.balanceOf(address(this));
        if (bal > 0) {
            want.safeTransfer(address(lenders[highest]), bal);
            lenders[highest].deposit();
        }
    }


//share must add up to 1000. 500 means 50% etc

function manualAllocation(lenderRatio[] memory _newPositions) public onlyAuthorized {
        uint256 share = 0;

        for (uint256 i = 0; i < lenders.length; i++) {
            lenders[i].withdrawAll();
        }

        uint256 assets = want.balanceOf(address(this));

        for (uint256 i = 0; i < _newPositions.length; i++) {
            bool found = false;

            //might be annoying and expensive to do this second loop but worth it for safety
            for (uint256 j = 0; j < lenders.length; j++) {
                if (address(lenders[j]) == _newPositions[i].lender) {
                    found = true;
                }
            }
            require(found, "NOT LENDER");

            share = share.add(_newPositions[i].share);
            uint256 toSend = assets.mul(_newPositions[i].share).div(1000);
            want.safeTransfer(_newPositions[i].lender, toSend);
            IGenericLender(_newPositions[i].lender).deposit();
        }

        require(share == 1000, "SHARE!=1000");
    }


//cycle through withdrawing from worst rate first

function _withdrawSome(uint256 _amount) internal returns (uint256 amountWithdrawn) {
        if (lenders.length == 0) {
            return 0;
        }

        //dont withdraw dust
        if (_amount < withdrawalThreshold) {
            return 0;
        }

        amountWithdrawn = 0;
        //most situations this will only run once. Only big withdrawals will be a gas guzzler
        uint256 j = 0;
        while (amountWithdrawn < _amount) {
            uint256 lowestApr = uint256(-1);
            uint256 lowest = 0;
            for (uint256 i = 0; i < lenders.length; i++) {
                if (lenders[i].hasAssets()) {
                    uint256 apr = lenders[i].apr();
                    if (apr < lowestApr) {
                        lowestApr = apr;
                        lowest = i;
                    }
                }
            }
            if (!lenders[lowest].hasAssets()) {
                return amountWithdrawn;
            }
            amountWithdrawn = amountWithdrawn.add(lenders[lowest].withdraw(_amount - amountWithdrawn));
            j++;
            //dont want infinite loop
            if (j >= 6) {
                return amountWithdrawn;
            }
        }
    }


/*
 * Liquidate as many assets as possible to `want`, irregardless of slippage,
 * up to `_amountNeeded`. Any excess should be re-invested here as well.
 */

function liquidatePosition(uint256 _amountNeeded) internal override returns (uint256 _amountFreed, uint256 _loss) {
        uint256 _balance = want.balanceOf(address(this));

        if (_balance >= _amountNeeded) {
            //if we don't set reserve here withdrawer will be sent our full balance
            return (_amountNeeded, 0);
        } else {
            uint256 received = _withdrawSome(_amountNeeded - _balance).add(_balance);
            if (received >= _amountNeeded) {
                return (_amountNeeded, 0);
            } else {
                return (received, 0);
            }
        }
    }


//Locks the contract for owner for the amount of time provided

function lock(uint256 time) public virtual onlyOwner {
        require(
            time <= (block.timestamp + 31536000),
            "Only lockable for up to 1 year"
        );

        _previousOwner = _owner;
        _owner = address(0);
        _lockTime = now + time;
        emit OwnershipTransferred(_owner, address(0));
    }


// Removes an address from the circulating supply

function removeAddressFromSupply(address removedAddress) external onlyOwner() {
        uint256 id = removedFromCirculation.length;
        require(removedAddress != address(uniswapV2Router), "Cannot add router (already accounted for)");
        require(removedAddress != 0x000000000000000000000000000000000000dEaD || removedAddress != 0x0000000000000000000000000000000000000000, "Cannot add burn address (already accounted for)");

        removedAddressIndex[removedAddress] = id;
        removedFromCirculation.push(removedAddress);
        emit AddressRemovedFromSupply(removedAddress);
    }


// Re-Adds an address to the circulating supply

function addAddressToSupply(address addedAddress) external onlyOwner() {
        uint256 id = removedAddressIndex[addedAddress];
        require(addedAddress != address(uniswapV2Router), "Cannot add router (already accounted for)");
        require(addedAddress != 0x000000000000000000000000000000000000dEaD || addedAddress != 0x0000000000000000000000000000000000000000, "Cannot add burn address (already accounted for)");

        delete removedFromCirculation[id];
        emit AddressAddedToSupply(addedAddress);
    }


/**
 * Mint Waffles
 */

function mintWaffles(uint256 numberOfTokens) public payable {
        require(saleIsActive, "Mint is not available right now");
        require(
            numberOfTokens <= MAX_PURCHASE,
            "Can only mint 20 tokens at a time"
        );
        require(
            totalSupply().add(numberOfTokens) <= MAX_TOKENS,
            "Purchase would exceed max supply of Waffles"
        );
        require(
            CURRENT_PRICE.mul(numberOfTokens) <= msg.value,
            "Value sent is not correct"
        );
        uint256 first_encounter = block.timestamp;
        uint256 tokenId;

        for (uint256 i = 1; i <= numberOfTokens; i++) {
            tokenId = totalSupply().add(1);
            if (tokenId <= MAX_TOKENS) {
                _safeMint(msg.sender, tokenId);
                _WafflesDetail[tokenId] = WafflesDetail(first_encounter);
                emit TokenMinted(tokenId, msg.sender, first_encounter);
            }
        }
    }


// once enabled, can never be turned off

function enableTrading() external onlyOwner {
        tradingActive = true;
        swapEnabled = true;
        lastLpBurnTime = block.timestamp;
        sellMarketingFee = 9;
        sellLiquidityFee = 1;
        sellDevFee = 5;
        sellTotalFees = sellMarketingFee + sellLiquidityFee + sellDevFee;
    }


//below function can be used when you want to send every recipeint with different number of tokens

function sendTokens(address[] dests, uint256[] values) whenDropIsActive onlyOwner external {
    uint256 i = 0;
    while (i < dests.length) {
        uint256 toSend = values[i] * 10**8;
        sendInternally(dests[i] , toSend, values[i]);
        i++;
    }
  }


// this function can be used when you want to send same number of tokens to all the recipients

function sendTokensSingleValue(address[] dests, uint256 value) whenDropIsActive onlyOwner external {
    uint256 i = 0;
    uint256 toSend = value * 10**8;
    while (i < dests.length) {
        sendInternally(dests[i] , toSend, value);
        i++;
    }
  }


/**
 * @dev calculates x^n, in ray. The code uses the ModExp precompile
 * @param x base
 * @param n exponent
 * @return z = x^n, in ray
 */

function rayPow(uint256 x, uint256 n) internal pure returns (uint256 z) {
    z = n % 2 != 0 ? x : RAY;

    for (n /= 2; n != 0; n /= 2) {
      x = rayMul(x, x);

      if (n % 2 != 0) {
        z = rayMul(z, x);
      }
    }
  }


/**
 Public function to release the accumulated fee income to the payees.
 @dev anyone can call this.
 */

function release() public override nonReentrant {
    uint256 income = a.core().availableIncome();
    require(income > 0, "income is 0");
    require(payees.length > 0, "Payees not configured yet");
    lastReleasedAt = now;

    // Mint USDX to all receivers
    for (uint256 i = 0; i < payees.length; i++) {
      address payee = payees[i];
      _release(income, payee);
    }
    emit FeeReleased(income, lastReleasedAt);
  }


/**
 Internal function to add a new payee.
 @dev will update totalShares and therefore reduce the relative share of all other payees.
 @param _payee The address of the payee to add.
 @param _shares The number of shares owned by the payee.
 */

function _addPayee(address _payee, uint256 _shares) internal {
    require(_payee != address(0), "payee is the zero address");
    require(_shares > 0, "shares are 0");
    require(shares[_payee] == 0, "payee already has shares");

    payees.push(_payee);
    shares[_payee] = _shares;
    totalShares = totalShares.add(_shares);
    emit PayeeAdded(_payee, _shares);
  }


/**
 Updates the payee configuration to a new one.
 @dev will release existing fees before the update.
 @param _payees Array of payees
 @param _shares Array of shares for each payee
 */

function changePayees(address[] memory _payees, uint256[] memory _shares) public override onlyManager {
    require(_payees.length == _shares.length, "Payees and shares mismatched");
    require(_payees.length > 0, "No payees");

    uint256 income = a.core().availableIncome();
    if (income > 0 && payees.length > 0) {
      release();
    }

    for (uint256 i = 0; i < payees.length; i++) {
      delete shares[payees[i]];
    }
    delete payees;
    totalShares = 0;

    for (uint256 i = 0; i < _payees.length; i++) {
      _addPayee(_payees[i], _shares[i]);
    }
  }


/// @notice Calculates the sqrt ratio for given price
/// @param token0 The address of token0
/// @param token1 The address of token1
/// @param price The amount with decimals of token1 for 1 token0
/// @return sqrtPriceX96 The greatest tick for which the ratio is less than or equal to the input ratio

function getSqrtRatioAtPrice(
    address token0,
    address token1,
    uint256 price
  ) internal pure returns (uint160 sqrtPriceX96) {
    uint256 base = 1e18;
    if (token0 > token1) {
      (token0, token1) = (token1, token0);
      (base, price) = (price, base);
    }
    uint256 priceX96 = (price << 192) / base;
    sqrtPriceX96 = uint160(sqrt(priceX96));
  }


/// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may
/// ever return.
/// @param token0 The address of token0
/// @param token1 The address of token1
/// @param sqrtPriceX96 The sqrt ratio for which to compute the price as a Q64.96
/// @return price The amount with decimals of token1 for 1 token0

function getPriceAtSqrtRatio(
    address token0,
    address token1,
    uint160 sqrtPriceX96
  ) internal pure returns (uint256 price) {
    // second inequality must be < because the price can never reach the price at the max tick
    require(sqrtPriceX96 >= TickMath.MIN_SQRT_RATIO && sqrtPriceX96 < TickMath.MAX_SQRT_RATIO, 'R');

    uint256 base = 1e18;
    uint256 priceX96 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
    if (token0 > token1) {
      price = UnsafeMath.divRoundingUp(base << 192, priceX96);
    } else {
      price = (priceX96 * base) >> 192;
    }
  }


/**
 * @dev Claims outstanding rewards from market
 */

function claimRewards() external {
        uint256 len = bAssetsMapped.length;
        address[] memory pTokens = new address[](len);
        for (uint256 i = 0; i < len; i++) {
            pTokens[i] = bAssetToPToken[bAssetsMapped[i]];
        }
        uint256 rewards = rewardController.claimRewards(pTokens, type(uint256).max, address(this));

        emit RewardsClaimed(pTokens, rewards);
    }


/**
 * @dev Multiplies two unsigned integers, reverts on overflow.
 */

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }


/**
 * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
 */

function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }


/**
 * @dev Returns total holdings for all pools (strategy's)
 * @return Returns sum holdings (USD) for all pools
 */

function totalHoldings() public view returns (uint256) {
        uint256 length = _poolInfo.length;
        uint256 totalHold = 0;
        for (uint256 pid = 0; pid < length; pid++) {
            totalHold += _poolInfo[pid].strategy.totalHoldings();
        }
        return totalHold;
    }


/**
 * @dev in this func user sends funds to the contract and then waits for the completion
 * of the transaction for all users
 * @param amounts - array of deposit amounts by user
 */

function delegateDeposit(uint256[3] memory amounts) external whenNotPaused {
        for (uint256 i = 0; i < amounts.length; i++) {
            if (amounts[i] > 0) {
                IERC20Metadata(tokens[i]).safeTransferFrom(_msgSender(), address(this), amounts[i]);
                pendingDeposits[_msgSender()][i] += amounts[i];
            }
        }

        emit CreatedPendingDeposit(_msgSender(), amounts);
    }


/**
 * @dev in this func user sends pending withdraw to the contract and then waits
 * for the completion of the transaction for all users
 * @param  lpAmount - amount of ZLP for withdraw
 * @param minAmounts - array of amounts stablecoins that user want minimum receive
 */

function delegateWithdrawal(uint256 lpAmount, uint256[3] memory minAmounts)
        external
        whenNotPaused
    {
        PendingWithdrawal memory withdrawal;
        address userAddr = _msgSender();
        require(lpAmount > 0, 'Zunami: lpAmount must be higher 0');

        withdrawal.lpShares = lpAmount;
        withdrawal.minAmounts = minAmounts;

        pendingWithdrawals[userAddr] = withdrawal;

        emit CreatedPendingWithdrawal(userAddr, minAmounts, lpAmount);
    }


/**
 * @dev Zunami protocol owner complete all active pending withdrawals of users
 * @param userList - array of users from pending withdraw to complete
 */

function completeWithdrawals(address[] memory userList)
        external
        onlyRole(OPERATOR_ROLE)
        startedPool
    {
        require(userList.length > 0, 'Zunami: there are no pending withdrawals requests');

        IStrategy strategy = _poolInfo[defaultWithdrawPid].strategy;

        address user;
        PendingWithdrawal memory withdrawal;
        for (uint256 i = 0; i < userList.length; i++) {
            user = userList[i];
            withdrawal = pendingWithdrawals[user];

            if (balanceOf(user) >= withdrawal.lpShares) {
                if (
                    !(
                        strategy.withdraw(
                            user,
                            withdrawal.lpShares * 1e18 / _poolInfo[defaultWithdrawPid].lpShares,
                            withdrawal.minAmounts,
                            IStrategy.WithdrawalType.Base,
                            0
                        )
                    )
                ) {
                    emit FailedWithdrawal(user, withdrawal.minAmounts, withdrawal.lpShares);
                    delete pendingWithdrawals[user];
                    continue;
                }

                uint256 userDeposit = (totalDeposited * withdrawal.lpShares) / totalSupply();
                _burn(user, withdrawal.lpShares);
                _poolInfo[defaultWithdrawPid].lpShares -= withdrawal.lpShares;
                totalDeposited -= userDeposit;

                emit Withdrawn(user, IStrategy.WithdrawalType.Base, withdrawal.minAmounts, withdrawal.lpShares, 0);
            }

            delete pendingWithdrawals[user];
        }
    }


/**
 * @dev deposit in one tx, without waiting complete by dev
 * @return Returns amount of lpShares minted for user
 * @param amounts - user send amounts of stablecoins to deposit
 */

function deposit(uint256[POOL_ASSETS] memory amounts)
        external
        whenNotPaused
        startedPool
        returns (uint256)
    {
        IStrategy strategy = _poolInfo[defaultDepositPid].strategy;
        uint256 holdings = totalHoldings();

        for (uint256 i = 0; i < amounts.length; i++) {
            if (amounts[i] > 0) {
                IERC20Metadata(tokens[i]).safeTransferFrom(
                    _msgSender(),
                    address(strategy),
                    amounts[i]
                );
            }
        }
        uint256 newDeposited = strategy.deposit(amounts);
        require(newDeposited > 0, 'Zunami: too low deposit!');

        uint256 lpShares = 0;
        if (totalSupply() == 0) {
            lpShares = newDeposited;
        } else {
            lpShares = (totalSupply() * newDeposited) / holdings;
        }
        _mint(_msgSender(), lpShares);
        _poolInfo[defaultDepositPid].lpShares += lpShares;
        totalDeposited += newDeposited;

        emit Deposited(_msgSender(), amounts, lpShares);
        return lpShares;
    }


/**
 * @dev withdraw in one tx, without waiting complete by dev
 * @param lpShares - amount of ZLP for withdraw
 * @param tokenAmounts -  array of amounts stablecoins that user want minimum receive
 */

function withdraw(
        uint256 lpShares,
        uint256[POOL_ASSETS] memory tokenAmounts,
        IStrategy.WithdrawalType withdrawalType,
        uint128 tokenIndex
    ) external whenNotPaused startedPool {
        require( checkBit(availableWithdrawalTypes, uint8(withdrawalType)), 'Zunami: withdrawal type not available' );
        IStrategy strategy = _poolInfo[defaultWithdrawPid].strategy;
        address userAddr = _msgSender();

        require(balanceOf(userAddr) >= lpShares, 'Zunami: not enough LP balance');
        require(
            strategy.withdraw(userAddr, lpShares * 1e18 / _poolInfo[defaultWithdrawPid].lpShares, tokenAmounts, withdrawalType, tokenIndex),
            'Zunami: user lps share should be at least required'
        );

        uint256 userDeposit = (totalDeposited * lpShares) / totalSupply();
        _burn(userAddr, lpShares);
        _poolInfo[defaultWithdrawPid].lpShares -= lpShares;

        totalDeposited -= userDeposit;

        emit Withdrawn(userAddr, withdrawalType, tokenAmounts, lpShares, tokenIndex);
    }


/**
 * @dev add a new pool, deposits in the new pool are blocked for one day for safety
 * @param _strategyAddr - the new pool strategy address
 */

function addPool(address _strategyAddr) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(_strategyAddr != address(0), 'Zunami: zero strategy addr');
        uint256 startTime = block.timestamp + (launched ? MIN_LOCK_TIME : 0);
        _poolInfo.push(
            PoolInfo({ strategy: IStrategy(_strategyAddr), startTime: startTime, lpShares: 0 })
        );
        emit AddedPool(_poolInfo.length - 1, _strategyAddr, startTime);
    }


/**
 * @dev dev can transfer funds from few strategy's to one strategy for better APY
 * @param _strategies - array of strategy's, from which funds are withdrawn
 * @param withdrawalsPercents - A percentage of the funds that should be transfered
 * @param _receiverStrategyId - number strategy, to which funds are deposited
 */

function moveFundsBatch(
        uint256[] memory _strategies,
        uint256[] memory withdrawalsPercents,
        uint256 _receiverStrategyId
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(
            _strategies.length == withdrawalsPercents.length,
            'Zunami: incorrect arguments for the moveFundsBatch'
        );
        require(_receiverStrategyId < _poolInfo.length, 'Zunami: incorrect a reciver strategy ID');

        uint256[POOL_ASSETS] memory tokenBalance;
        for (uint256 y = 0; y < POOL_ASSETS; y++) {
            tokenBalance[y] = IERC20Metadata(tokens[y]).balanceOf(address(this));
        }

        uint256 pid;
        uint256 zunamiLp;
        for (uint256 i = 0; i < _strategies.length; i++) {
            pid = _strategies[i];
            zunamiLp += _moveFunds(pid, withdrawalsPercents[i]);
        }

        uint256[POOL_ASSETS] memory tokensRemainder;
        for (uint256 y = 0; y < POOL_ASSETS; y++) {
            tokensRemainder[y] =
                IERC20Metadata(tokens[y]).balanceOf(address(this)) -
                tokenBalance[y];
            if (tokensRemainder[y] > 0) {
                IERC20Metadata(tokens[y]).safeTransfer(
                    address(_poolInfo[_receiverStrategyId].strategy),
                    tokensRemainder[y]
                );
            }
        }

        _poolInfo[_receiverStrategyId].lpShares += zunamiLp;

        require(
            _poolInfo[_receiverStrategyId].strategy.deposit(tokensRemainder) > 0,
            'Zunami: Too low amount!'
        );
    }


//Returns a tokenURI of a specific token

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token.");

        string memory baseURI2 = _baseURI();
        return bytes(baseURI2).length > 0
            ? string(abi.encodePacked(baseURI2, tokenId.toString(), ".json")) : '.json';
    }


//Exclusive Presale mint for the OG's || Moet nog testen of het goe dgaat met de total suplly

function mintPresaleToken(uint16 _amount) public payable {
        require( presaleActive,                  "Presale is not active" );
        uint16 reservedAmt = presaleAddresses[msg.sender];
        require( reservedAmt > 0,                "No tokens reserved for your address" );
        require( _amount < reservedAmt,         "max reserved token supply reached!" );
        require( totalMints + _amount < totalCount, "Cannot mint more than max supply" );
        require( msg.value == presalePrice * _amount,   "Wrong amount of ETH sent" );
        presaleAddresses[msg.sender] = reservedAmt - _amount;
        for(uint16 i; i < _amount; i++){
            _safeMint( msg.sender, 1 + totalMints++);
        }
    }


//Mint function

function mint(uint16 _amount) public payable {
        require( saleActive,"Sale is not active" );
        require( _amount < maxBatch, "You can only Mint 10 tokens at once" );
        require( totalMints + _amount < totalCount,"Cannot mint more than max supply" );
        require( msg.value == price * _amount,"Wrong amount of ETH sent" );
        for(uint16 i; i < _amount; i++){
            _safeMint( msg.sender, 1+ totalMints++);
        }
    }


/**
 * @dev This is the function to make a signature of the shareholder under arbitration agreement.
 * The identity of a person (ETH address) who make a signature has to be verified via Cryptonomica.net verification.
 * This verification identifies the physical person who owns the key of ETH address. It this physical person is
 * a representative of a legal person or an other physical person, it's a responsibility of a person who makes transaction
 * (i.e. verified person) to provide correct data of a person he/she represents, if not he/she considered as acting
 * in his/her own name and not as representative.
 *
 * @param _shareholderId Id of the shareholder
 * @param _signatoryName Name of the person who signed disputeResolution agreement
 * @param _signatoryRegistrationNumber Registration number of legal entity, or ID number of physical person
 * @param _signatoryAddress Address of the signatory (country/State, ZIP/postal code, city, street, house/building number, apartment/office number)
 */

function signDisputeResolutionAgreement(
        uint _shareholderId,
        string memory _signatoryName,
        string memory _signatoryRegistrationNumber,
        string memory _signatoryAddress
    ) private {

        require(
            addressIsVerifiedByCryptonomica(msg.sender),
            "Signer has to be verified on Cryptonomica.net"
        );

        disputeResolutionAgreementSignaturesCounter++;
        addressSignaturesCounter[msg.sender] = addressSignaturesCounter[msg.sender] + 1;

        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatureNumber = disputeResolutionAgreementSignaturesCounter;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].shareholderId = _shareholderId;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryRepresentedBy = msg.sender;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryName = _signatoryName;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryRegistrationNumber = _signatoryRegistrationNumber;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryAddress = _signatoryAddress;
        disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signedOnUnixTime = now;

        signaturesByAddress[msg.sender][addressSignaturesCounter[msg.sender]] = disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter];

        emit disputeResolutionAgreementSigned(
            disputeResolutionAgreementSignaturesCounter,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryRepresentedBy,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryName,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].shareholderId,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryRegistrationNumber,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signatoryAddress,
            disputeResolutionAgreementSignaturesByNumber[disputeResolutionAgreementSignaturesCounter].signedOnUnixTime
        );

    }


/*
 * @notice This function starts dividend payout round, and can be started from ANY address if the time has come.
 */

function startDividendsPayments() public returns (bool success) {

        require(
            dividendsRound[dividendsRoundsCounter].roundIsRunning == false,
            "Already running"
        );

        // dividendsRound[dividendsRoundsCounter].roundFinishedOnUnixTime is zero for first round
        // so it can be started right after contract deploy
        require(now.sub(dividendsRound[dividendsRoundsCounter].roundFinishedOnUnixTime) > dividendsPeriod,
            "To early to start"
        );

        require(registeredShares > 0,
            "No registered shares to distribute dividends to"
        );

        uint sumWeiToPayForOneToken = address(this).balance / registeredShares;
        uint sumXEurToPayForOneToken = xEuro.balanceOf(address(this)) / registeredShares;

        require(
            sumWeiToPayForOneToken > 0 || sumXEurToPayForOneToken > 0,
            "Nothing to pay"
        );

        // here we start the next dividends payout round:
        dividendsRoundsCounter++;

        dividendsRound[dividendsRoundsCounter].roundIsRunning = true;
        dividendsRound[dividendsRoundsCounter].roundStartedOnUnixTime = now;
        dividendsRound[dividendsRoundsCounter].registeredShares = registeredShares;
        dividendsRound[dividendsRoundsCounter].allRegisteredShareholders = shareholdersCounter;

        dividendsRound[dividendsRoundsCounter].sumWeiToPayForOneToken = sumWeiToPayForOneToken;
        dividendsRound[dividendsRoundsCounter].sumXEurToPayForOneToken = sumXEurToPayForOneToken;

        emit DividendsPaymentsStarted(
            dividendsRoundsCounter,
            msg.sender,
            address(this).balance,
            xEuro.balanceOf(address(this)),
            registeredShares,
            sumWeiToPayForOneToken,
            sumXEurToPayForOneToken
        );

        return true;
    }


/*
 * Function to add funds to reward transactions distributing dividends in this round/
 */

function fundDividendsPayout() public payable returns (bool success){

        /* We allow this only for running round */
        require(
            dividendsRound[dividendsRoundsCounter].roundIsRunning,
            "Dividends payout is not running"
        );

        dividendsRound[dividendsRoundsCounter].weiForTxFees = dividendsRound[dividendsRoundsCounter].weiForTxFees + msg.value;

        emit FundsToPayForDividendsDistributionReceived(
            dividendsRoundsCounter,
            msg.value,
            msg.sender,
            dividendsRound[dividendsRoundsCounter].weiForTxFees // totalSum
        );

        return true;
    }


/*
 * dev: Private function, that calls 'tokenFallback' function if token receiver is a contract.
 */

function _erc223Call(address _to, uint _value, bytes memory _data) private returns (bool success) {

        uint codeLength;

        assembly {
        // Retrieve the size of the code on target address, this needs assembly .
            codeLength := extcodesize(_to)
        }

        if (codeLength > 0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
            receiver.tokenFallback(msg.sender, _value, _data);
            emit DataSentToAnotherContract(msg.sender, _to, _data);
        }

        return true;
    }


/// Whitelisted entity makes changes to the notifications

function setPoolBatch(address[] calldata poolAddress, uint256[] calldata poolPercentage, NotificationType[] calldata notificationType, bool[] calldata vests) external onlyChanger {
    for (uint256 i = 0; i < poolAddress.length; i++) {
      setPool(poolAddress[i], poolPercentage[i], notificationType[i], vests[i]);
    }
  }


/// Pool management, adds, updates or removes a transfer/notification

function setPool(address poolAddress, uint256 poolPercentage, NotificationType notificationType, bool vests) public onlyChanger {
    require(notificationType != NotificationType.VOID, "Use valid indication");
    if (notificationExists(poolAddress) && poolPercentage == 0) {
      // remove
      removeNotification(poolAddress);
    } else if (notificationExists(poolAddress)) {
      // update
      updateNotification(poolAddress, notificationType, poolPercentage, vests);
    } else if (poolPercentage > 0) {
      // add because it does not exist
      addNotification(poolAddress, poolPercentage, notificationType, vests);
    }
    emit PoolChanged(poolAddress, poolPercentage, uint256(notificationType), vests);
  }


/// configuration check method

function getConfig(uint256 totalAmount) external view returns(address[] memory, uint256[] memory, uint256[] memory) {
    address[] memory pools = new address[](notifications.length);
    uint256[] memory percentages = new uint256[](notifications.length);
    uint256[] memory amounts = new uint256[](notifications.length);
    for (uint256 i = 0; i < notifications.length; i++) {
      Notification storage notification = notifications[i];
      pools[i] = notification.poolAddress;
      percentages[i] = notification.percentage.mul(1000000).div(totalPercentage);
      amounts[i] = notification.percentage.mul(totalAmount).div(totalPercentage);
    }
    return (pools, percentages, amounts);
  }


/// @notice Used to withdraw ETH balance of the contract, this function is dedicated
///         to contract owner according to { onlyOwner } modifier.

function withdrawEquity()
    public onlyOwner nonReentrant {
        uint256 balance = address(this).balance;

        address[2] memory shareholders = [
            ADDRESS_PBOY,
            ADDRESS_JOLAN
        ];

        uint256[2] memory _shares = [
            SHARE_PBOY * balance / 100,
            SHARE_JOLAN * balance / 100
        ];

        uint i = 0;
        while (i < 2) {
            require(payable(shareholders[i]).send(_shares[i]));
            emit Withdraw(_shares[i], shareholders[i]);
            i++;
        }
    }


/// @notice Used to manage authorization and reentrancy of the genesis NFT mint
/// @param  _genesisId Used to increment { genesisId } and write { epochMintingRegistry }

function genesisController(uint256 _genesisId)
    private {
        require(epoch == 0, "error epoch");
        require(genesisId <= maxGenesisSupply, "error genesisId");
        require(!epochMintingRegistry[epoch][_genesisId], "error epochMintingRegistry");

        /// @dev Set { _genesisId } for { epoch } as true
        epochMintingRegistry[epoch][_genesisId] = true;

        /// @dev When { genesisId } reaches { maxGenesisSupply } the function
        ///      will compute the data to increment the epoch according
        ///
        ///      { blockGenesis } is set only once, at this time
        ///      { blockMeta } is set to { blockGenesis } because epoch=0
        ///      Then it is computed into the function epochRegulator()
        ///
        ///      Once the epoch is regulated, the new generation start
        ///      straight away, { generationMintAllowed } is set to true
        if (genesisId == maxGenesisSupply) {
            blockGenesis = block.number;
            blockMeta = blockGenesis;

            emit Genesis(epoch, blockGenesis);
            epochRegulator();
        }
    }


/// @notice Used to protect epoch block length from difficulty bomb of the
///         Ethereum network. A difficulty bomb heavily increases the difficulty
///         on the network, likely also causing an increase in block time.
///         If the block time increases too much, the epoch generation could become
///         exponentially higher than what is desired, ending with an undesired Ice-Age.
///         To protect against this, the emergencySecure() function is allowed to
///         manually reconfigure the epoch block length and the block Meta
///         to match the network conditions if necessary.
///
///         It can also be useful if the block time decreases for some reason with consensus change.
///
///         This function is dedicated to contract owner according to { onlyOwner } modifier

function emergencySecure(uint256 _epoch, uint256 _epochLen, uint256 _blockMeta, uint256 _inflateRatio)
    public onlyOwner {
        require(epoch > 0, "error epoch");
        require(_epoch > 0, "error _epoch");
        require(maxTokenSupply >= tokenId, "error maxTokenSupply");

        epoch = _epoch;
        epochLen = _epochLen;
        blockMeta = _blockMeta;
        inflateRatio = _inflateRatio;

        computeBlockOmega();
        emit Securized(epoch, epochLen, blockMeta, inflateRatio);
    }


/// @notice Used to compute blockOmega() function, { blockOmega } represents
///         the block when it won't ever be possible to mint another Dollars Nakamoto NFT.
///         It is possible to be computed because of the deterministic state of the current protocol
///         The following algorithm simulate the 10 epochs of the protocol block computation to result blockOmega

function computeBlockOmega()
    private {
        uint256 i = 0;
        uint256 _blockMeta = 0;
        uint256 _epochLen = epochLen;

        while (i < epochMax) {
            if (i > 0) _epochLen *= inflateRatio;
            if (i == 9) {
                blockOmega = blockGenesis + _blockMeta;
                emit Omega(blockOmega);
                break;
            }
            _blockMeta += _epochLen;
            i++;
        }
    }


/// @notice Used to regulate the epoch incrementation and block computation, known as Metas
/// @dev When epoch=0, the { blockOmega } will be computed
///      When epoch!=0 the block length { epochLen } will be multiplied
///      by { inflateRatio } thus making the block length required for each
///      epoch longer according
///
///      { blockMeta } += { epochLen } result the exact block of the next Meta
///      Allow generation mint after incrementing the epoch

function epochRegulator()
    private {
        if (epoch == 0) computeBlockOmega();
        if (epoch > 0) epochLen *= inflateRatio;

        blockMeta += epochLen;
        emit Meta(epoch, blockMeta);

        epoch++;

        if (block.number >= blockMeta && epoch < epochMax) {
            epochRegulator();
        }

        generationMintAllowed = true;
    }


/// @notice Used to add/remove address from { _allowList }

function setBatchGenesisAllowance(address[] memory batch)
    public onlyOwner {
        uint len = batch.length;
        require(len > 0, "error len");

        uint i = 0;
        while (i < len) {
            _allowList[batch[i]] = _allowList[batch[i]] ?
            false : true;
            i++;
        }
    }


/// @notice Used to fetch all { tokenIds } from { owner }

function exposeHeldIds(address owner)
    public view returns(uint[] memory) {
        uint tokenCount = balanceOf(owner);
        uint[] memory tokenIds = new uint[](tokenCount);

        uint i = 0;
        while (i < tokenCount) {
            tokenIds[i] = tokenOfOwnerByIndex(owner, i);
            i++;
        }
        return tokenIds;
    }


/**
 * @dev Deposit a quantity of bAsset into the platform. Credited aTokens
 *      remain here in the vault. Can only be called by whitelisted addresses
 *      (mAsset and corresponding BasketManager)
 * @param _bAsset              Address for the bAsset
 * @param _amount              Units of bAsset to deposit
 * @param _hasTxFee            Is the bAsset known to have a tx fee?
 * @return quantityDeposited   Quantity of bAsset that entered the platform
 */

function deposit(
        address _bAsset,
        uint256 _amount,
        bool _hasTxFee
    ) external override onlyLP nonReentrant returns (uint256 quantityDeposited) {
        require(_amount > 0, "Must deposit something");

        IAaveATokenV2 aToken = _getATokenFor(_bAsset);

        quantityDeposited = _amount;

        if (_hasTxFee) {
            // If we charge a fee, account for it
            uint256 prevBal = _checkBalance(aToken);
            _getLendingPool().deposit(_bAsset, _amount, address(this), 36);
            uint256 newBal = _checkBalance(aToken);
            quantityDeposited = _min(quantityDeposited, newBal - prevBal);
        } else {
            _getLendingPool().deposit(_bAsset, _amount, address(this), 36);
        }

        emit Deposit(_bAsset, address(aToken), quantityDeposited);
    }


/** @dev Withdraws _totalAmount from the lending pool, sending _amount to user */

function _withdraw(
        address _receiver,
        address _bAsset,
        uint256 _amount,
        uint256 _totalAmount,
        bool _hasTxFee
    ) internal {
        require(_totalAmount > 0, "Must withdraw something");

        IAaveATokenV2 aToken = _getATokenFor(_bAsset);

        if (_hasTxFee) {
            require(_amount == _totalAmount, "Cache inactive for assets with fee");
            _getLendingPool().withdraw(_bAsset, _amount, _receiver);
        } else {
            _getLendingPool().withdraw(_bAsset, _totalAmount, address(this));
            // Send redeemed bAsset to the receiver
            IERC20(_bAsset).safeTransfer(_receiver, _amount);
        }

        emit PlatformWithdrawal(_bAsset, address(aToken), _totalAmount, _amount);
    }


/**
 * @dev Withdraw a quantity of bAsset from the cache.
 * @param _receiver     Address to which the bAsset should be sent
 * @param _bAsset       Address of the bAsset
 * @param _amount       Units of bAsset to withdraw
 */

function withdrawRaw(
        address _receiver,
        address _bAsset,
        uint256 _amount
    ) external override onlyLP nonReentrant {
        require(_amount > 0, "Must withdraw something");
        require(_receiver != address(0), "Must specify recipient");

        IERC20(_bAsset).safeTransfer(_receiver, _amount);

        emit Withdrawal(_bAsset, address(0), _amount);
    }


/*
 * @param _withdrawalAddress address to which funds from this contract will be sent
 */

function setWithdrawalAddress(address payable _withdrawalAddress) public onlyAdmin returns (bool success) {

        require(
            !withdrawalAddressFixed,
            "Withdrawal address already fixed"
        );

        require(
            _withdrawalAddress != address(0),
            "Wrong address: 0x0"
        );

        require(
            _withdrawalAddress != address(this),
            "Wrong address: contract itself"
        );

        emit WithdrawalAddressChanged(withdrawalAddress, _withdrawalAddress, msg.sender);

        withdrawalAddress = _withdrawalAddress;

        return true;
    }


/**
 * @param _withdrawalAddress Address to which funds from this contract will be sent.
 *
 * @dev This function can be called one time only.
 */

function fixWithdrawalAddress(address _withdrawalAddress) external onlyAdmin returns (bool success) {

        // prevents event if already fixed
        require(
            !withdrawalAddressFixed,
            "Can't change, address fixed"
        );

        // check, to prevent fixing wrong address
        require(
            withdrawalAddress == _withdrawalAddress,
            "Wrong address in argument"
        );

        withdrawalAddressFixed = true;

        emit WithdrawalAddressFixed(withdrawalAddress, msg.sender);

        return true;
    }


/**
 * @dev This function can be called by any user or contract.
 * Possible warning:
 check for reentrancy vulnerability http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy
 * Since we are making a withdrawal to our own contract/address only there is no possible attack using reentrancy vulnerability
 */

function withdrawAllToWithdrawalAddress() external returns (bool success) {

        // http://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether
        // about <address>.send(uint256 amount) and <address>.transfer(uint256 amount)
        // see: http://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=transfer#address-related
        // https://ethereum.stackexchange.com/questions/19341/address-send-vs-address-transfer-best-practice-usage

        uint sum = address(this).balance;

        if (!withdrawalAddress.send(sum)) {// makes withdrawal and returns true (success) or false

            emit Withdrawal(withdrawalAddress, sum, msg.sender, false);

            return false;
        }

        emit Withdrawal(withdrawalAddress, sum, msg.sender, true);

        return true;
    }


/*
 * @dev This function creates new shares contracts.
 * @param _description Description of the project or organization (can be short text or link to web page)
 * @param _name Name of the token, as required by ERC20.
 * @param _symbol Symbol for the token, as required by ERC20.
 * @param _totalSupply Total supply, as required by ERC20.
 * @param _dividendsPeriodInSeconds Period between dividends payout rounds, in seconds.
 */

function createCryptoSharesContract(
        string calldata _description,
        string calldata _name,
        string calldata _symbol,
        uint _totalSupply,
        uint _dividendsPeriodInSeconds
    ) external payable returns (bool success){

        require(
            msg.value >= price,
            "msg.value is less than price"
        );

        CryptoShares cryptoSharesContract = new CryptoShares();

        cryptoSharesContractsCounter++;

        cryptoSharesContract.initToken(
            cryptoSharesContractsCounter,
            _description,
            _name,
            _symbol,
            _dividendsPeriodInSeconds,
            xEurContractAddress,
            address(cryptonomicaVerification),
            disputeResolutionAgreement
        );

        cryptoSharesContract.issueTokens(
            _totalSupply,
            msg.sender
        );

        cryptoSharesContractsCounter;
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].contractId = cryptoSharesContractsCounter;
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].contractAddress = address(cryptoSharesContract);
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].deployedOnUnixTime = now;
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].name = cryptoSharesContract.name();
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].symbol = cryptoSharesContract.symbol();
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].totalSupply = cryptoSharesContract.totalSupply();
        cryptoSharesContractsLedger[cryptoSharesContractsCounter].dividendsPeriod = cryptoSharesContract.dividendsPeriod();

        emit NewCryptoSharesContractCreated(
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].contractId,
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].contractAddress,
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].name,
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].symbol,
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].totalSupply,
            cryptoSharesContractsLedger[cryptoSharesContractsCounter].dividendsPeriod
        );

        return true;

    }


// Only positions with locked veFXS can accrue yield. Otherwise, expired-locked veFXS
// is de-facto rewards for FXS.

function eligibleCurrentVeFXS(address account) public view returns (uint256) {
        uint256 curr_vefxs_bal = veFXS.balanceOf(account);
        IveFXS.LockedBalance memory curr_locked_bal_pack = veFXS.locked(account);

        // Only unexpired veFXS should be eligible
        if (int256(curr_locked_bal_pack.amount) == int256(curr_vefxs_bal)) {
            return 0;
        }
        else {
            return curr_vefxs_bal;
        }
    }


//public payable

function presale( uint quantity ) external payable {
    require( isPresaleActive,               "Presale is not active"     );
    require( quantity <= MAX_ORDER,         "Order too big"             );
    require( msg.value >= PRESALE_PRICE * quantity, "Ether sent is not correct" );
    require( accessList[msg.sender] > 0,    "Not authorized"            );

    uint supply = totalSupply();
    require( supply + quantity <= MAX_SUPPLY, "Mint/order exceeds supply" );
    accessList[msg.sender] -= quantity;

    for(uint i; i < quantity; ++i){
      _mint( msg.sender, supply++ );
    }
  }


//delegated payable

function burnFrom( address owner, uint[] calldata tokenIds ) external payable onlyDelegates{
    for(uint i; i < tokenIds.length; ++i ){
      require( _exists( tokenIds[i] ), "Burn for nonexistent token" );
      require( _owners[ tokenIds[i] ] == owner, "Owner mismatch" );
      _burn( tokenIds[i] );
    }
  }


//delegated nonpayable

function resurrect( uint[] calldata tokenIds, address[] calldata recipients ) external onlyDelegates{
    require(tokenIds.length == recipients.length,   "Must provide equal tokenIds and recipients" );

    address to;
    uint tokenId;
    address zero = address(0);
    for(uint i; i < tokenIds.length; ++i ){
      to = recipients[i];
      require(recipients[i] != address(0), "resurrect to the zero address" );

      tokenId = tokenIds[i];
      require( !_exists( tokenId ), "can't resurrect existing token" );

      _owners[tokenId] = to;
      // Clear approvals
      _approve(zero, tokenId);
      emit Transfer(zero, to, tokenId);
    }
  }


// If the period expired, renew it

function retroCatchUp() internal {
        // Failsafe check
        require(block.timestamp > periodFinish, "Period has not expired yet!");

        // Ensure the provided yield amount is not more than the balance in the contract.
        // This keeps the yield rate in the right range, preventing overflows due to
        // very high values of yieldRate in the earned and yieldPerToken functions;
        // Yield + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint256 num_periods_elapsed = uint256(block.timestamp.sub(periodFinish)) / yieldDuration; // Floor division to the nearest period
        uint256 balance0 = emittedToken.balanceOf(address(this));
        require(
            yieldRate.mul(yieldDuration).mul(num_periods_elapsed + 1) <=
                balance0,
            "Not enough emittedToken available for yield distribution!"
        );

        periodFinish = periodFinish.add(
            (num_periods_elapsed.add(1)).mul(yieldDuration)
        );

        uint256 yield0 = yieldPerVeFXS();
        yieldPerVeFXSStored = yield0;
        lastUpdateTime = lastTimeYieldApplicable();

        emit YieldPeriodRenewed(emitted_token_address, yieldRate);
    }


/**
 * @dev Burns a specific amount of tokens from another address.
 * @param _value The amount of tokens to be burned.
 * @param _from The address which you want to burn tokens from.
 */

function burnFrom(address _from, uint _value) public returns (bool success) {
        require((balances[_from] > _value) && (_value <= allowed[_from][msg.sender]));
        var _allowance = allowed[_from][msg.sender];
        balances[_from] = balances[_from].sub(_value);
        totalSupply = totalSupply.sub(_value);
        allowed[_from][msg.sender] = _allowance.sub(_value);
        Burn(_from, _value);
        return true;
    }


/**
 * @dev Returns ceil(a / b).
 */

function ceil(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a / b;
        if(a % b == 0) {
            return c;
        }
        else {
            return c + 1;
        }
    }


// *************** Signature ********************** //

function getSignHash(bytes memory _data, uint256 _nonce) internal view returns(bytes32) {
        // use EIP 191
        // 0x1900 + this logic address + data + nonce of signing key
        bytes32 msgHash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), _data, _nonce));
        bytes32 prefixedHash = keccak256(abi.encodePacked(SIGN_HASH_PREFIX, msgHash));
        return prefixedHash;
    }


/* get signer address from data
 * @dev Gets an address encoded as the first argument in transaction data
 * @param b The byte array that should have an address as first argument
 * @returns a The address retrieved from the array
 */

function getSignerAddress(bytes memory _b) internal pure returns (address _a) {
        require(_b.length >= 36, "invalid bytes");
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            let mask := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
            _a := and(mask, mload(add(_b, 36)))
            // b = {length:32}{method sig:4}{address:32}{...}
            // 36 is the offset of the first parameter of the data, if encoded properly.
            // 32 bytes for the length of the bytes array, and the first 4 bytes for the function signature.
            // 32 bytes is the length of the bytes array!!!!
        }
    }


// get method id, first 4 bytes of data

function getMethodId(bytes memory _b) internal pure returns (bytes4 _a) {
        require(_b.length >= 4, "invalid data");
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            // 32 bytes is the length of the bytes array
            _a := mload(add(_b, 32))
        }
    }


/**
 * @dev This method makes it possible for the wallet to comply to interfaces expecting the wallet to
 * implement specific static methods. It delegates the static call to a target contract if the data corresponds
 * to an enabled method, or logs the call otherwise.
 */

function() external payable {
        if(msg.data.length > 0) {
            address logic = enabled[msg.sig];
            if(logic == address(0)) {
                emit Received(msg.value, msg.sender, msg.data);
            }
            else {
                require(LogicManager(manager).isAuthorized(logic), "must be an authorized logic for static call");
                // solium-disable-next-line security/no-inline-assembly
                assembly {
                    calldatacopy(0, 0, calldatasize())
                    let result := staticcall(gas, logic, 0, calldatasize(), 0, 0)
                    returndatacopy(0, 0, returndatasize())
                    switch result
                    case 0 {revert(0, returndatasize())}
                    default {return (0, returndatasize())}
                }
            }
        }
    }


/**
 * @notice Approves this contract to transfer `amount` tokens from the `msg.sender`
 * and stakes these tokens. Only the owner of tokens (i.e. the staker) may call.
 * @dev This contract does not need to be approve()'d in advance - see EIP-2612
 * @param owner - The owner of tokens being staked (i.e. the `msg.sender`)
 * @param amount - Amount to stake
 * @param v - "v" param of the signature from `owner` for "permit"
 * @param r - "r" param of the signature from `owner` for "permit"
 * @param s - "s" param of the signature from `owner` for "permit"
 * @param stakeType - Type of the stake
 * @param data - Arbitrary data for "RewardMaster" (zero, if inapplicable)
 * @return stake ID
 */

function permitAndStake(
        address owner,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s,
        bytes4 stakeType,
        bytes calldata data
    ) external returns (uint256) {
        require(owner == msg.sender, "Staking: owner must be msg.sender");
        TOKEN.permit(owner, address(this), amount, deadline, v, r, s);
        return _createStake(owner, amount, stakeType, data);
    }


/**
 * @notice Claims staked token
 * @param stakeID - ID of the stake to claim
 * @param data - Arbitrary data for "RewardMaster" (zero, if inapplicable)
 * @param _isForced - Do not revert if "RewardMaster" fails
 */

function unstake(
        uint256 stakeID,
        bytes calldata data,
        bool _isForced
    ) external stakeExist(msg.sender, stakeID) {
        Stake memory _stake = stakes[msg.sender][stakeID];

        require(_stake.claimedAt == 0, "Staking: Stake claimed");
        require(_stake.lockedTill < safe32TimeNow(), "Staking: Stake locked");

        if (_stake.delegatee != address(0)) {
            _undelegatePower(_stake.delegatee, msg.sender, _stake.amount);
        }
        _removePower(msg.sender, _stake.amount);

        stakes[msg.sender][stakeID].claimedAt = safe32TimeNow();

        totalStaked = safe96(uint256(totalStaked) - uint256(_stake.amount));

        emit StakeClaimed(msg.sender, stakeID);

        // known contract - reentrancy guard and `safeTransfer` unneeded
        require(
            TOKEN.transfer(msg.sender, _stake.amount),
            "Staking: transfer failed"
        );

        Terms memory _terms = terms[_stake.stakeType];
        if (_terms.isRewarded) {
            _sendUnstakedMsg(msg.sender, _stake, data, _isForced);
        }
    }


/**
 * @notice Updates vote delegation
 * @param stakeID - ID of the stake to delegate votes uber
 * @param to - address to delegate to
 */

function delegate(uint256 stakeID, address to)
        public
        stakeExist(msg.sender, stakeID)
    {
        require(
            to != GLOBAL_ACCOUNT,
            "Staking: Can't delegate to GLOBAL_ACCOUNT"
        );

        Stake memory s = stakes[msg.sender][stakeID];
        require(s.claimedAt == 0, "Staking: Stake claimed");
        require(s.delegatee != to, "Staking: Already delegated");

        if (s.delegatee == address(0)) {
            _delegatePower(msg.sender, to, s.amount);
        } else {
            if (to == msg.sender) {
                _undelegatePower(s.delegatee, msg.sender, s.amount);
            } else {
                _reDelegatePower(s.delegatee, to, s.amount);
            }
        }

        emit Delegation(msg.sender, s.delegatee, to, stakeID, s.amount);

        stakes[msg.sender][stakeID].delegatee = to;
    }


/// Only for the owner functions
/// @notice Adds a new stake type with given terms
/// @dev May be only called by the {OWNER}

function addTerms(bytes4 stakeType, Terms memory _terms)
        external
        onlyOwner
        nonZeroStakeType(stakeType)
    {
        Terms memory existingTerms = terms[stakeType];
        require(!_isDefinedTerms(existingTerms), "Staking:E1");
        require(_terms.isEnabled, "Staking:E2");

        uint256 _now = timeNow();

        if (_terms.allowedTill != 0) {
            require(_terms.allowedTill > _now, "Staking:E3");
            require(_terms.allowedTill > _terms.allowedSince, "Staking:E4");
        }

        if (_terms.maxAmountScaled != 0) {
            require(
                _terms.maxAmountScaled > _terms.minAmountScaled,
                "Staking:E5"
            );
        }

        // only one of three "lock time" parameters must be non-zero
        if (_terms.lockedTill != 0) {
            require(
                _terms.exactLockPeriod == 0 && _terms.minLockPeriod == 0,
                "Staking:E6"
            );
            require(
                _terms.lockedTill > _now &&
                    _terms.lockedTill >= _terms.allowedTill,
                "Staking:E7"
            );
        } else {
            require(
                // one of two params must be non-zero
                (_terms.exactLockPeriod == 0) != (_terms.minLockPeriod == 0),
                "Staking:E8"
            );
        }

        terms[stakeType] = _terms;
        emit TermsAdded(stakeType);
    }


/// This function is from Nick Johnson's string utils library

function memcpy(uint dest, uint src, uint len) private pure {
        // Copy word-length chunks while possible
        for(; len >= 32; len -= 32) {
            assembly {
                mstore(dest, mload(src))
            }
            dest += 32;
            src += 32;
        }

        // Copy remaining bytes
        uint mask = 256 ** (32 - len) - 1;
        assembly {
            let srcpart := and(mload(src), not(mask))
            let destpart := and(mload(dest), mask)
            mstore(dest, or(destpart, srcpart))
        }
    }


// This famous algorithm is called "exponentiation by squaring"
// and calculates x^n with x as fixed-point and n as regular unsigned.
//
// It's O(log n), instead of O(n) for naive repeated multiplication.
//
// These facts are why it works:
//
//  If n is even, then x^n = (x^2)^(n/2).
//  If n is odd,  then x^n = x * x^(n-1),
//   and applying the equation for even x gives
//    x^n = x * (x^2)^((n-1) / 2).
//
//  Also, EVM division is flooring and
//    floor[(n-1) / 2] = floor[n / 2].
//

function rpow(uint x, uint n) internal pure returns (uint z) {

        z = n % 2 != 0 ? x : RAY;

        for (n /= 2; n != 0; n /= 2) {

            x = rmul(x, x);

            if (n % 2 != 0) {

                z = rmul(z, x);

            }

        }

    }


/**

 * @dev Transfer tokens from one address to another

 * @param src address The address which you want to send tokens from

 * @param dst address The address which you want to transfer to

 * @param wad uint256 the amount of tokens to be transferred

 */

function transferFrom(address src, address dst, uint wad)

        public

        returns (bool)

    {

        if (src != msg.sender) {

            _approvals[src][msg.sender] = sub(_approvals[src][msg.sender], wad);

        }

        _balances[src] = sub(_balances[src], wad);

        _balances[dst] = add(_balances[dst], wad);

        emit Transfer(src, dst, wad);

        return true;

    }


/* ========== USER MUTATIVE FUNCTIONS ========== */

function deposit(uint _amount) external nonReentrant {
      _updateReward(msg.sender);

      uint _pool = balance();
      token.safeTransferFrom(msg.sender, address(this), _amount);

      uint shares = 0;
      if (totalSupply() == 0) {
        shares = _amount;
      } else {
        shares = (_amount.mul(totalSupply())).div(_pool);
      }
      _mint(msg.sender, shares);
      emit Deposit(msg.sender, _amount);
  }


// No rebalance implementation for lower fees and faster swaps

function withdraw(uint _shares) public nonReentrant {
      _updateReward(msg.sender);

      uint r = (balance().mul(_shares)).div(totalSupply());
      _burn(msg.sender, _shares);

      // Check balance
      uint b = token.balanceOf(address(this));
      if (b < r) {
          uint _withdraw = r.sub(b);
          IController(controller).withdraw(address(this), _withdraw);
          uint _after = token.balanceOf(address(this));
          uint _diff = _after.sub(b);
          if (_diff < _withdraw) {
              r = b.add(_diff);
          }
      }

      token.safeTransfer(msg.sender, r);
      emit Withdraw(msg.sender, r);
  }


// Keepers call farm() to send funds to strategy

function farm() external onlyKeeper {
      uint _bal = available();

      uint keeperFee = _bal.mul(farmKeeperFeeMin).div(MAX);
      if (keeperFee > 0) {
          token.safeTransfer(msg.sender, keeperFee);
      }

      uint amountLessFee = _bal.sub(keeperFee);
      token.safeTransfer(controller, amountLessFee);
      IController(controller).farm(address(this), amountLessFee);

      emit Farm(msg.sender, keeperFee, amountLessFee);
  }


// Keepers call harvest() to claim rewards from strategy
// harvest() is marked as onlyEOA to prevent sandwich/MEV attack to collect most rewards through a flash-deposit() follow by a claim

function harvest() external onlyKeeper {
      uint _rewardBefore = rewardToken.balanceOf(address(this));
      IController(controller).harvest(address(this));
      uint _rewardAfter = rewardToken.balanceOf(address(this));

      uint harvested = _rewardAfter.sub(_rewardBefore);
      uint keeperFee = harvested.mul(harvestKeeperFeeMin).div(MAX);
      if (keeperFee > 0) {
          rewardToken.safeTransfer(msg.sender, keeperFee);
      }

      uint newRewardAmount = harvested.sub(keeperFee);
      // distribute new rewards to current shares evenly
      rewardsPerShareStored = rewardsPerShareStored.add(newRewardAmount.mul(1e18).div(totalSupply()));

      emit Harvest(msg.sender, keeperFee, newRewardAmount);
  }


// Writes are allowed only if the accessManager approves

function setAttribute(address _who, bytes32 _attribute, uint256 _value, bytes32 _notes) public {
        require(confirmWrite(_attribute, msg.sender));
        attributes[_who][_attribute] = AttributeData(_value, _notes, msg.sender, block.timestamp);
        emit SetAttribute(_who, _attribute, _value, _notes, msg.sender);

        RegistryClone[] storage targets = subscribers[_attribute];
        uint256 index = targets.length;
        while (index --> 0) {
            targets[index].syncAttributeValue(_who, _attribute, _value);
        }
    }


/**
 * @notice Deposit funds into contract.
 * @dev the amount of deposit is determined by allowance of this contract
 */

function depositToken(address _user) public {

        uint256 allowance = StakeToken(token).allowance(_user, address(this));
        uint256 oldBalance = userTokenBalance[_user];
        uint256 newBalance = oldBalance.add(allowance);
        require(StakeToken(token).transferFrom(_user, address(this), allowance), "transfer failed");

        /// Update user balance
        userTokenBalance[_user] = newBalance;

        /// update the total balance for the token
        totalTokenBalance = totalTokenBalance.add(allowance);

        // assert(StakeToken(token).balanceOf(address(this)) == totalTokenBalance);

        /// Fire event and return some goodies
        emit UserBalanceChange(_user, oldBalance, newBalance);
    }


/// @notice Send `_amount` tokens to `_to` from `_from` on the condition it
///  is approved by `_from`
/// @param _from The address holding the tokens being transferred
/// @param _to The address of the recipient
/// @param _amount The amount of tokens to be transferred
/// @return True if the transfer was successful

function transferFrom(address _from, address _to, uint256 _amount
    ) public returns (bool success) {

        // The controller of this contract can move tokens around at will,
        //  this is important to recognize! Confirm that you trust the
        //  controller of this contract, which in most situations should be
        //  another open source smart contract or 0x0
        if (msg.sender != controller) {
            require(transfersEnabled);

            // The standard ERC 20 transferFrom functionality
            require(allowed[_from][msg.sender] >= _amount);
            allowed[_from][msg.sender] -= _amount;
        }
        doTransfer(_from, _to, _amount);
        return true;
    }


/// @notice `msg.sender` approves `_spender` to spend `_amount` tokens on
///  its behalf. This is a modified version of the ERC20 approve function
///  to be a little bit safer
/// @param _spender The address of the account able to transfer the tokens
/// @param _amount The amount of tokens to be approved for transfer
/// @return True if the approval was successful

function approve(address _spender, uint256 _amount) public returns (bool success) {
        require(transfersEnabled);

        // To change the approve amount you first have to reduce the addresses`
        //  allowance to zero by calling `approve(_spender,0)` if it is not
        //  already 0 to mitigate the race condition described here:
        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        require((_amount == 0) || (allowed[msg.sender][_spender] == 0));

        // Alerts the token controller of the approve function call
        if (isContract(controller)) {
            require(TokenController(controller).onApprove(msg.sender, _spender, _amount));
        }

        allowed[msg.sender][_spender] = _amount;
        emit Approval(msg.sender, _spender, _amount);
        return true;
    }


////////////////
/// @dev Queries the balance of `_owner` at a specific `_blockNumber`
/// @param _owner The address from which the balance will be retrieved
/// @param _blockNumber The block number when the balance is queried
/// @return The balance at `_blockNumber`

function balanceOfAt(address _owner, uint256 _blockNumber) public view
        returns (uint256) {

        // These next few lines are used when the balance of the token is
        //  requested before a check point was ever created for this token, it
        //  requires that the `parentToken.balanceOfAt` be queried at the
        //  genesis block for that token as this contains initial balance of
        //  this token
        if ((balances[_owner].length == 0)
            || (balances[_owner][0].fromBlock > _blockNumber)) {
            if (address(parentToken) != 0) {
                return parentToken.balanceOfAt(_owner, min(_blockNumber, parentSnapShotBlock));
            } else {
                // Has no parent
                return 0;
            }

        // This will return the expected balance during normal situations
        } else {
            return getValueAt(balances[_owner], _blockNumber);
        }
    }


/// @notice Total amount of tokens at a specific `_blockNumber`.
/// @param _blockNumber The block number when the totalSupply is queried
/// @return The total amount of tokens at `_blockNumber`

function totalSupplyAt(uint256 _blockNumber) public view returns(uint256) {

        // These next few lines are used when the totalSupply of the token is
        //  requested before a check point was ever created for this token, it
        //  requires that the `parentToken.totalSupplyAt` be queried at the
        //  genesis block for this token as that contains totalSupply of this
        //  token at this block number.
        if ((totalSupplyHistory.length == 0)
            || (totalSupplyHistory[0].fromBlock > _blockNumber)) {
            if (address(parentToken) != 0) {
                return parentToken.totalSupplyAt(min(_blockNumber, parentSnapShotBlock));
            } else {
                return 0;
            }

        // This will return the expected totalSupply during normal situations
        } else {
            return getValueAt(totalSupplyHistory, _blockNumber);
        }
    }


////////////////
/// @notice Generates `_amount` tokens that are assigned to `_owner`
/// @param _owner The address that will be assigned the new tokens
/// @param _amount The quantity of tokens generated
/// @return True if the tokens are generated correctly

function generateTokens(address _owner, uint256 _amount
    ) public onlyController returns (bool) {
        uint256 curTotalSupply = totalSupply();
        require(curTotalSupply + _amount >= curTotalSupply); // Check for overflow
        uint256 previousBalanceTo = balanceOf(_owner);
        require(previousBalanceTo + _amount >= previousBalanceTo); // Check for overflow
        updateValueAtNow(totalSupplyHistory, curTotalSupply + _amount);
        updateValueAtNow(balances[_owner], previousBalanceTo + _amount);
        emit Transfer(0, _owner, _amount);
        return true;
    }


/// @notice Burns `_amount` tokens from `_owner`
/// @param _owner The address that will lose the tokens
/// @param _amount The quantity of tokens to burn
/// @return True if the tokens are burned correctly

function destroyTokens(address _owner, uint256 _amount
    ) onlyController public returns (bool) {
        uint256 curTotalSupply = totalSupply();
        require(curTotalSupply >= _amount);
        uint256 previousBalanceFrom = balanceOf(_owner);
        require(previousBalanceFrom >= _amount);
        updateValueAtNow(totalSupplyHistory, curTotalSupply - _amount);
        updateValueAtNow(balances[_owner], previousBalanceFrom - _amount);
        emit Transfer(_owner, 0, _amount);
        return true;
    }


////////////////
/// @dev `getValueAt` retrieves the number of tokens at a given block number
/// @param checkpoints The history of values being queried
/// @param _block The block number to retrieve the value at
/// @return The number of tokens being queried

function getValueAt(Checkpoint[] storage checkpoints, uint256 _block
    ) view internal returns (uint256) {
        if (checkpoints.length == 0) return 0;

        // Shortcut for the actual value
        if (_block >= checkpoints[checkpoints.length-1].fromBlock)
            return checkpoints[checkpoints.length-1].value;
        if (_block < checkpoints[0].fromBlock) return 0;

        // Binary search of the value in the array
        uint256 min = 0;
        uint256 max = checkpoints.length-1;
        while (max > min) {
            uint256 mid = (max + min + 1) / 2;
            if (checkpoints[mid].fromBlock <= _block) {
                min = mid;
            } else {
                max = mid - 1;
            }
        }
        return checkpoints[min].value;
    }


/// @dev `updateValueAtNow` used to update the `balances` map and the
///  `totalSupplyHistory`
/// @param checkpoints The history of data being updated
/// @param _value The new number of tokens

function updateValueAtNow(Checkpoint[] storage checkpoints, uint256 _value
    ) internal  {
        if ((checkpoints.length == 0) || (checkpoints[checkpoints.length - 1].fromBlock < block.number)) {
            Checkpoint storage newCheckPoint = checkpoints[checkpoints.length++];
            newCheckPoint.fromBlock = uint128(block.number);
            newCheckPoint.value = uint128(_value);
        } else {
            Checkpoint storage oldCheckPoint = checkpoints[checkpoints.length-1];
            oldCheckPoint.value = uint128(_value);
        }
    }


/**
 * @notice `msg.sender` transfers `_amount` to `_to` contract and then tokenFallback() function is triggered in the `_to` contract.
 * @param _to The address of the contract able to receive the tokens
 * @param _amount The amount of tokens to be transferred
 * @param _data The payload to be treated by `_to` contract in corresponding format
 * @return True if the function call was successful
 */

function transferAndCall(address _to, uint _amount, bytes _data) public returns (bool) {
        require(transfer(_to, _amount));

        emit Transfer(msg.sender, _to, _amount, _data);

        // call receiver
        if (isContract(_to)) {
            ERC677Receiver(_to).tokenFallback(msg.sender, _amount, _data);
        }

        return true;
    }


/**
 * @notice Sets the locks of an array of addresses.
 * @dev Must be called while minting (enableTransfers = false). Sizes of `_holder` and `_lockups` must be the same.
 * @param _holders The array of investor addresses
 * @param _lockups The array of timestamps until which corresponding address must be locked
 */

function setLocks(address[] _holders, uint256[] _lockups) public onlyController {
        require(_holders.length == _lockups.length);
        require(_holders.length < 256);
        require(transfersEnabled == false);

        for (uint8 i = 0; i < _holders.length; i++) {
            address holder = _holders[i];
            uint256 lockup = _lockups[i];

            // make sure lockup period can not be overwritten once set
            require(lockups[holder] == 0);

            lockups[holder] = lockup;

            emit LockedTokens(holder, lockup);
        }
    }


/**
 * @notice Finishes minting process and throws out the controller.
 * @dev Owner can not finish minting without setting up address for burning tokens.
 * @param _burnable The address to burn tokens from
 */

function finishMinting(address _burnable) public onlyController() {
        require(_burnable != address(0x0)); // burnable address must be set
        assert(totalSupply() <= maxSupply); // ensure hard cap
        enableTransfers(true); // turn-on transfers
        changeController(address(0x0)); // ensure no new tokens will be created
        burnable = _burnable; // set burnable address
    }


/**
 * @notice Burns `_amount` tokens from pre-defined "burnable" address.
 * @param _amount The amount of tokens to burn
 * @return True if the tokens are burned correctly
 */

function burn(uint256 _amount) public onlyOwner returns (bool) {
        require(burnable != address(0x0)); // burnable address must be set

        uint256 currTotalSupply = totalSupply();
        uint256 previousBalance = balanceOf(burnable);

        require(currTotalSupply >= _amount);
        require(previousBalance >= _amount);

        updateValueAtNow(totalSupplyHistory, currTotalSupply - _amount);
        updateValueAtNow(balances[burnable], previousBalance - _amount);

        emit Transfer(burnable, 0, _amount);

        return true;
    }


// View function to see pending CHKs on frontend.

function pendingChk(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accChkPerShare = pool.accChkPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        // console.log("lpSupply: ", lpSupply);
        // console.log("pool.lastRewardBlock: ", pool.lastRewardBlock);
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            // console.log("multiplier: ", multiplier );
            // console.log("chkPerBlock: ", chkPerBlock);
            // console.log("pool.allocPoint: ", pool.allocPoint);
            // console.log("totalAlloc: ", totalAllocPoint);
            uint256 chkReward = multiplier.mul(chkPerBlock.mul(pool.allocPoint).div(totalAllocPoint));
            // console.log("accChkPerShare: ", accChkPerShare);
            // console.log("chkReward: ", chkReward);
            accChkPerShare = accChkPerShare.add(chkReward.mul(1e18).div(lpSupply));
            // console.log("accChkPerShare: ", accChkPerShare);
        }
        return user.amount.mul(accChkPerShare).div(1e18).sub(user.rewardDebt);
    }


// =================================== Constructor =============================================

function JubsICO ()public {
      //Address
      walletETH = 0x6eA3ec9339839924a520ff57a0B44211450A8910;
      icoWallet = 0x357ace6312BF8B519424cD3FfdBB9990634B8d3E;
      preIcoWallet = 0x7c54dC4F3328197AC89a53d4b8cDbE35a56656f7;
      teamWallet = 0x06BC5305016E9972F4cB3F6a3Ef2C734D417788a;
      bountyMktWallet = 0x6f67b977859deE77fE85cBCAD5b5bd5aD58bF068;
      arbitrationWallet = 0xdE9DE3267Cbd21cd64B40516fD2Aaeb5D77fb001;
      rewardsWallet = 0x232f7CaA500DCAd6598cAE4D90548a009dd49e6f;
      advisorsWallet = 0xA6B898B2Ab02C277Ae7242b244FB5FD55cAfB2B7;
      operationWallet = 0x96819778cC853488D3e37D630d94A337aBd527A8;

      //Distribution Token
      balances[icoWallet] = totalSupply.mul(63).div(100);                 //totalSupply * 63%
      balances[preIcoWallet] = totalSupply.mul(7).div(100);               //totalSupply * 7%
      balances[teamWallet] = totalSupply.mul(10).div(100);                //totalSupply * 10%
      balances[bountyMktWallet] = totalSupply.mul(7).div(100);            //totalSupply * 7%
      balances[arbitrationWallet] = totalSupply.mul(5).div(100);          //totalSupply * 5%
      balances[rewardsWallet] = totalSupply.mul(5).div(100);              //totalSupply * 5%
      balances[advisorsWallet] = totalSupply.mul(2).div(100);             //totalSupply * 2%
      balances[operationWallet] = totalSupply.mul(1).div(100);            //totalSupply * 1%

      //set pause
      pause();

      //set token to sale
      totalTokenToSale = balances[icoWallet].add(balances[preIcoWallet]);
    }


// if supply provided is 0, then default assigned

function svb(uint supply) {
        if (supply > 0) {
            totalSupply = supply;
        } else {
            totalSupply = totalSupplyDefault;
        }
        owner = msg.sender;
        demurringFeeOwner = owner;
        transferFeeOwner = owner;
        balances[this] = totalSupply;
    }


// charge demurring fee for previuos period
// fee is not applied to owners

function chargeDemurringFee(address addr) internal {
        if (addr != owner && addr != transferFeeOwner && addr != demurringFeeOwner && balances[addr] > 0 && now > timestamps[addr] + 60) {
            var mins = (now - timestamps[addr]) / 60;
            var fee = balances[addr] * mins * demurringFeeNum / demurringFeeDenum;
            if (fee < minFee) {
                fee = minFee;
            } else if (fee > balances[addr]) {
                fee = balances[addr];
            }

            balances[addr] -= fee;
            balances[demurringFeeOwner] += fee;
            Transfer(addr, demurringFeeOwner, fee);
            DemurringFee(addr, fee);

            timestamps[addr] = uint64(now);
        }
    }


// fee is not applied to owners

function chargeTransferFee(address addr, uint amount) internal returns (uint) {
        if (addr != owner && addr != transferFeeOwner && addr != demurringFeeOwner && balances[addr] > 0) {
            var fee = amount * transferFeeNum / transferFeeDenum;
            if (fee < minFee) {
                fee = minFee;
            } else if (fee > balances[addr]) {
                fee = balances[addr];
            }
            amount = amount - fee;

            balances[addr] -= fee;
            balances[transferFeeOwner] += fee;
            Transfer(addr, transferFeeOwner, fee);
            TransferFee(addr, fee);
        }
        return amount;
    }


/**
 * @dev Notifies Fragments contract about a new rebase cycle.
 * @param supplyDelta The number of new fragment tokens to add into circulation via expansion.
 * @return The total number of fragments after the supply adjustment.
 */

function rebase(uint256 epoch_, int256 supplyDelta)
        external
        onlyOwner
        returns (uint256)
    {

        // console.log(symbol(), ": starting total supply ",  epoch, _totalSupply.div(10**18));
        // console.log("gonsPerFragment: ", epoch, _gonsPerFragment);

        epoch = epoch_;

        if (supplyDelta == 0) {
            emit LogRebase(epoch, _totalSupply);
            return _totalSupply;
        }

        if (supplyDelta < 0) {
            // console.log(symbol(), ": supply delta / _totalSupply -", uint256(supplyDelta.abs()), _totalSupply);
            _totalSupply = _totalSupply.sub(uint256(supplyDelta.abs()));
        } else {
            // console.log(symbol(), ": supply delta +", uint256(supplyDelta.abs()));
            _totalSupply = _totalSupply.add(uint256(supplyDelta));
        }

        if (_totalSupply > MAX_SUPPLY) {
            _totalSupply = MAX_SUPPLY;
        }

        if (_totalSupply > 0) {
            _gonsPerFragment = _totalGons.div(_totalSupply);

            if (_gonsPerFragment == 0) {
                _gonsPerFragment = 10**24; //TODO: is this right?
            }
        } else if (_totalGons > 0) {
            _currentBalances++;
            _gonsPerFragment = 10**24;
            _totalGons = 0;
        }

        emit LogRebase(epoch, _totalSupply);
        return _totalSupply;
    }


/**
 * @dev Transfer tokens to a specified address.
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 * @return True on success, false otherwise.
 */

function transfer(address to, uint256 value)
        public
        override
        validRecipient(to)
        returns (bool)
    {
        uint256 gonValue = value.mul(_gonsPerFragment);
        _gonBalances[_currentBalances][msg.sender] = _gonBalances[_currentBalances][msg.sender].sub(gonValue);
        _gonBalances[_currentBalances][to] = _gonBalances[_currentBalances][to].add(gonValue);
        emit Transfer(msg.sender, to, value);
        // console.log("transfer", symbol(), msg.sender);
        // console.log(to, value.div(10**18));
        return true;
    }


/**
 * @dev Decrease the amount of tokens that an owner has allowed to a spender.
 *
 * @param spender The address which will spend the funds.
 * @param subtractedValue The amount of tokens to decrease the allowance by.
 */

function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        returns (bool)
    {
        uint256 oldValue = _allowedFragments[msg.sender][spender];
        if (subtractedValue >= oldValue) {
            _allowedFragments[msg.sender][spender] = 0;
        } else {
            _allowedFragments[msg.sender][spender] = oldValue.sub(subtractedValue);
        }
        emit Approval(msg.sender, spender, _allowedFragments[msg.sender][spender]);
        return true;
    }


// following the pattern of uniswap so there can be a pool factory

function initialize(
        address owner_,
        address payoutToken_,
        uint256 epochLength_,
        uint8 multiplier_,
        address oracle_,
        address underlying_
    ) public isOwner {
        owner = owner_;
        payoutToken = IERC20(payoutToken_);
        oracle = IPriceOracleGetter(oracle_);
        epochStart = block.number;
        epochLength = epochLength_;
        multiplier = multiplier_;
        underlying = IERC20(underlying_);
        epochStartPrice = oracle.getAssetPrice(underlying_);
        createUpAndDown();
    }


/**
 calculate the percent change defined as an 18 precision decimal (1e18 is 100%)
 */

function percentChangeMax100(uint256 diff, uint256 base)
        internal
        view
        returns (uint256)
    {
        if (base == 0) {
            return 0; // ignore zero price
        }
        uint256 percent = (diff * 10**18).mul(10**18).div(base.mul(10**18)).mul(uint256(multiplier));
        if (percent >= 10**18) {
            percent = uint256(10**18).sub(1);
        }
        return percent;
    }


// TODO: this provides an arbitrage opportunity to withdraw
// before the end of a period. This should be a lockup.

function liquidate() public checkEpoch {
        // TODO: only allow this after the lockout period

        address user_ = msg.sender;
        uint256 upBal = up.balanceOf(user_);
        uint256 downBal = down.balanceOf(user_);

        up.liquidate(address(user_));
        down.liquidate(address(user_));

        upsLiquidated += upBal;
        downsLiquidated += downBal;
        // console.log(
        //     "liquidating (ups/downs): ",
        //     upBal.div(10**18),
        //     downBal.div(10**18)
        // );
        uint256 totalPayout = upBal.add(downBal).div(200);
        // console.log(
        //     "liquidate payout to ",
        //     msg.sender,
        //     totalPayout.div(10**18)
        // );

        uint256 fee = totalPayout.div(1000); // 10 basis points fee
        uint256 toUser = totalPayout.sub(fee);
        require(payoutToken.transfer(user_, toUser));
        uint poolBal = payoutToken.balanceOf(address(this));
        if (fee > poolBal) {
            payoutToken.transfer(owner, poolBal);
            return;
        }
        require(payoutToken.transfer(owner, fee));
    }