// Update fees on buys

function updateBuyFees(uint256 _marketingFee, uint256 _liquidityFee, uint256 _developmentFee) external onlyOwner {
        buyMarketingFee = _marketingFee;
        buyLiquidityFee = _liquidityFee;
        buyDevelopmentFee = _developmentFee;
        buyTotalFees = buyMarketingFee + buyLiquidityFee + buyDevelopmentFee;
        require(buyTotalFees <= 9, "Must keep fees at 9% or less");
    }


// Update fees on sells

function updateSellFees(uint256 _marketingFee, uint256 _liquidityFee, uint256 _developmentFee) external onlyOwner {
        sellMarketingFee = _marketingFee;
        sellLiquidityFee = _liquidityFee;
        sellDevelopmentFee = _developmentFee;
        sellTotalFees = sellMarketingFee + sellLiquidityFee + sellDevelopmentFee;
        require(sellTotalFees <= 18, "Must keep fees at 18% or less");
    }


// Contract sells

function swapBack() private {
        uint256 contractBalance = balanceOf(address(this));
        uint256 totalTokensToSwap = tokensForLiquidity + tokensForMarketing + tokensForDevelopment;

        if (contractBalance == 0 || totalTokensToSwap == 0) {
            return;
        }

        if (contractBalance > swapTokensAtAmount * 10) {
            contractBalance = swapTokensAtAmount * 10;
        }

        bool success;
        uint256 liquidityTokens = contractBalance * tokensForLiquidity / totalTokensToSwap / 2;

        swapTokensForEth(contractBalance - liquidityTokens);

        uint256 ethBalance = address(this).balance;
        uint256 ethForLiquidity = ethBalance;
        uint256 ethForMarketing = ethBalance * tokensForMarketing / (totalTokensToSwap - (tokensForLiquidity / 2));
        uint256 ethForDevelopment = ethBalance * tokensForDevelopment / (totalTokensToSwap - (tokensForLiquidity / 2));

        ethForLiquidity -= ethForMarketing + ethForDevelopment;

        tokensForLiquidity = 0;
        tokensForMarketing = 0;
        tokensForDevelopment = 0;

        if (liquidityTokens > 0 && ethForLiquidity > 0) {
            addLiquidity(liquidityTokens, ethForLiquidity);
        }

        (success,) = address(DevelopmentAddress).call{value: ethForDevelopment}("");
        (success,) = address(MarketingAddress).call{value: address(this).balance}("");
    }


// Withdraw unnecessary tokens

function transferForeignToken(address _token, address _to) external onlyOwner returns (bool _sent) {
        require(_token != address(0), "_token address cannot be 0");
        uint256 _contractBalance = IERC20(_token).balanceOf(address(this));
        _sent = IERC20(_token).transfer(_to, _contractBalance);
        emit TransferForeignToken(_token, _contractBalance);
    }


// For the trustee to issue an offer of new tokens to a holder

function issue(address _addr, uint _amount)
        public
        canEnter
        onlyTrustee
        returns (bool)
    {
        // prevent overflows in total supply
        assert(totalSupply + _amount < MAXTOKENS);

        join(_addr);
        Holder holder = holders[_addr];
        holder.offerAmount = _amount;
        holder.offerExpiry = uint40(now + 7 days);
        IssueOffer(_addr);
        return true;
    }


// To close a holder account

function vacate(address _addr)
        public
        canEnter
        isHolder(msg.sender)
        isHolder(_addr)
        returns (bool)
    {
        Holder holder = holders[_addr];
        // Ensure holder account is empty, is not the trustee and there are no
        // pending transactions or dividends
        require(_addr != trustee);
        require(holder.tokenBalance == 0);
        require(holder.etherBalance == 0);
        require(holder.lastClaimed == dividendPoints);
        require(ptxHead == ptxTail);

        delete holderIndex[holder.id];
        delete holders[_addr];
        // NB can't garbage collect holder.allowances mapping
        return (true);
    }


// performs presale burn

function burn (uint256 _burnAmount, bool _presaleBurn) public onlyOwner returns (bool success) {
    if (_presaleBurn) {
      require(_presaleBurnTotal.add(_burnAmount) <= _maximumPresaleBurnAmount);
      _presaleBurnTotal = _presaleBurnTotal.add(_burnAmount);
      _transfer(_owner, address(0), _burnAmount);
      _totalSupply = _totalSupply.sub(_burnAmount);
    } else {
      _transfer(_owner, address(0), _burnAmount);
      _totalSupply = _totalSupply.sub(_burnAmount);
    }
    return true;
  }


/**
 * @notice Allows an user to create an unique Vault
 * @dev Only one vault per address can be created
 */

function createVault() external virtual whenNotPaused {
    require(
      userToVault[msg.sender] == 0,
      "VaultHandler::createVault: vault already created"
    );

    uint256 id = counter.current();
    userToVault[msg.sender] = id;
    Vault memory vault = Vault(id, 0, 0, msg.sender);
    vaults[id] = vault;
    counter.increment();
    emit VaultCreated(msg.sender, id);
  }


/**
 * @notice Allows users to add collateral to their vaults
 * @param _amount of collateral to be added
 * @dev _amount should be higher than 0
 * @dev ERC20 token must be approved first
 */

function addCollateral(uint256 _amount)
    external
    virtual
    nonReentrant
    vaultExists
    whenNotPaused
    notZero(_amount)
  {
    require(
      collateralContract.transferFrom(msg.sender, address(this), _amount),
      "VaultHandler::addCollateral: ERC20 transfer did not succeed"
    );

    Vault storage vault = vaults[userToVault[msg.sender]];
    vault.Collateral = vault.Collateral.add(_amount);
    emit CollateralAdded(msg.sender, vault.Id, _amount);
  }


/**
 * @notice Allows users to remove collateral currently not being used to generate TCAP tokens from their vaults
 * @param _amount of collateral to remove
 * @dev reverts if the resulting ratio is less than the minimun ratio
 * @dev _amount should be higher than 0
 * @dev transfers the collateral back to the user
 */

function removeCollateral(uint256 _amount)
    external
    virtual
    nonReentrant
    vaultExists
    whenNotPaused
    notZero(_amount)
  {
    Vault storage vault = vaults[userToVault[msg.sender]];
    uint256 currentRatio = getVaultRatio(vault.Id);

    require(
      vault.Collateral >= _amount,
      "VaultHandler::removeCollateral: retrieve amount higher than collateral"
    );

    vault.Collateral = vault.Collateral.sub(_amount);
    if (currentRatio != 0) {
      require(
        getVaultRatio(vault.Id) >= ratio,
        "VaultHandler::removeCollateral: collateral below min required ratio"
      );
    }
    require(
      collateralContract.transfer(msg.sender, _amount),
      "VaultHandler::removeCollateral: ERC20 transfer did not succeed"
    );
    emit CollateralRemoved(msg.sender, vault.Id, _amount);
  }


/**
 * @notice Uses collateral to generate debt on TCAP Tokens which are minted and assigend to caller
 * @param _amount of tokens to mint
 * @dev _amount should be higher than 0
 * @dev requires to have a vault ratio above the minimum ratio
 * @dev if reward handler is set stake to earn rewards
 */

function mint(uint256 _amount)
    external
    virtual
    nonReentrant
    vaultExists
    whenNotPaused
    notZero(_amount)
  {
    Vault storage vault = vaults[userToVault[msg.sender]];
    uint256 collateral = requiredCollateral(_amount);

    require(
      vault.Collateral >= collateral,
      "VaultHandler::mint: not enough collateral"
    );

    vault.Debt = vault.Debt.add(_amount);
    require(
      getVaultRatio(vault.Id) >= ratio,
      "VaultHandler::mint: collateral below min required ratio"
    );

    if (address(rewardHandler) != address(0)) {
      rewardHandler.stake(msg.sender, _amount);
    }

    TCAPToken.mint(msg.sender, _amount);
    emit TokensMinted(msg.sender, vault.Id, _amount);
  }


/**
 * @notice Pays the debt of TCAP tokens resulting them on burn, this releases collateral up to minimun vault ratio
 * @param _amount of tokens to burn
 * @dev _amount should be higher than 0
 * @dev A fee of exactly burnFee must be sent as value on ETH
 * @dev The fee goes to the treasury contract
 * @dev if reward handler is set exit rewards
 */

function burn(uint256 _amount)
    external
    payable
    virtual
    nonReentrant
    vaultExists
    whenNotPaused
    notZero(_amount)
  {
    uint256 fee = getFee(_amount);
    require(
      msg.value >= fee,
      "VaultHandler::burn: burn fee less than required"
    );

    Vault memory vault = vaults[userToVault[msg.sender]];

    _burn(vault.Id, _amount);

    if (address(rewardHandler) != address(0)) {
      rewardHandler.withdraw(msg.sender, _amount);
      rewardHandler.getRewardFromVault(msg.sender);
    }
    safeTransferETH(treasury, fee);

    //send back ETH above fee
    safeTransferETH(msg.sender, msg.value.sub(fee));
    emit TokensBurned(msg.sender, vault.Id, _amount);
  }


/**
 * @notice Allow users to burn TCAP tokens to liquidate vaults with vault collateral ratio under the minium ratio, the liquidator receives the staked collateral of the liquidated vault at a premium
 * @param _vaultId to liquidate
 * @param _maxTCAP max amount of TCAP the liquidator is willing to pay to liquidate vault
 * @dev Resulting ratio must be above or equal minimun ratio
 * @dev A fee of exactly burnFee must be sent as value on ETH
 * @dev The fee goes to the treasury contract
 */

function liquidateVault(uint256 _vaultId, uint256 _maxTCAP)
    external
    payable
    nonReentrant
    whenNotPaused
  {
    Vault storage vault = vaults[_vaultId];
    require(vault.Id != 0, "VaultHandler::liquidateVault: no vault created");

    uint256 vaultRatio = getVaultRatio(vault.Id);
    require(
      vaultRatio < ratio,
      "VaultHandler::liquidateVault: vault is not liquidable"
    );

    uint256 requiredTCAP = requiredLiquidationTCAP(vault.Id);
    require(
      _maxTCAP >= requiredTCAP,
      "VaultHandler::liquidateVault: liquidation amount different than required"
    );

    uint256 fee = getFee(requiredTCAP);
    require(
      msg.value >= fee,
      "VaultHandler::liquidateVault: burn fee less than required"
    );

    uint256 reward = liquidationReward(vault.Id);
    _burn(vault.Id, requiredTCAP);

    //Removes the collateral that is rewarded to liquidator
    vault.Collateral = vault.Collateral.sub(reward);

    // Triggers update of CTX Rewards
    if (address(rewardHandler) != address(0)) {
      rewardHandler.withdraw(vault.Owner, requiredTCAP);
    }

    require(
      collateralContract.transfer(msg.sender, reward),
      "VaultHandler::liquidateVault: ERC20 transfer did not succeed"
    );
    safeTransferETH(treasury, fee);

    //send back ETH above fee
    safeTransferETH(msg.sender, msg.value.sub(fee));
    emit VaultLiquidated(vault.Id, msg.sender, requiredTCAP, reward);
  }


/**
 * @notice Returns the minimal required TCAP to liquidate a Vault
 * @param _vaultId of the vault to liquidate
 * @return amount required of the TCAP Token
 * @dev LT = ((((D * r) / 100) - cTcap) * 100) / (r - (p + 100))
 * cTcap = ((C * cp) / P)
 * LT = Required TCAP
 * D = Vault Debt
 * C = Required Collateral
 * P = TCAP Token Price
 * cp = Collateral Price
 * r = Min Vault Ratio
 * p = Liquidation Penalty
 */

function requiredLiquidationTCAP(uint256 _vaultId)
    public
    view
    virtual
    returns (uint256 amount)
  {
    Vault memory vault = vaults[_vaultId];
    uint256 tcapPrice = TCAPPrice();
    uint256 collateralPrice = getOraclePrice(collateralPriceOracle);
    uint256 collateralTcap =
      (vault.Collateral.mul(collateralPrice)).div(tcapPrice);
    uint256 reqDividend =
      (((vault.Debt.mul(ratio)).div(100)).sub(collateralTcap)).mul(100);
    uint256 reqDivisor = ratio.sub(liquidationPenalty.add(100));
    amount = reqDividend.div(reqDivisor);
  }


/**
 * @notice Returns the Collateral Ratio of the Vault
 * @param _vaultId id of vault
 * @return currentRatio
 * @dev vr = (cp * (C * 100)) / D * P
 * vr = Vault Ratio
 * C = Vault Collateral
 * cp = Collateral Price
 * D = Vault Debt
 * P = TCAP Token Price
 */

function getVaultRatio(uint256 _vaultId)
    public
    view
    virtual
    returns (uint256 currentRatio)
  {
    Vault memory vault = vaults[_vaultId];
    if (vault.Id == 0 || vault.Debt == 0) {
      currentRatio = 0;
    } else {
      uint256 collateralPrice = getOraclePrice(collateralPriceOracle);
      currentRatio = (
        (collateralPrice.mul(vault.Collateral.mul(100))).div(
          vault.Debt.mul(TCAPPrice())
        )
      );
    }
  }


/**
 * @dev Get the raw trait indices for a given tokenId
 * @param tokenId of a MEV Army NFT
 * @return An array of integers that are indices for the trait arrays stored in this contract
 *   example: [ binary index, legion index, light index, mouth index, helmet index, eyes index, faces index ]
 **/

function getTraitsIndices(uint256 tokenId) public view returns (EditionIndices memory){
        require(tokenId > 0 && tokenId < 10000, "nonexistent tokenId");

        // calculate the slot for a given token id
        uint256 slotForTokenId = (tokenId - 1) >> 2;

        // calculate the index within a slot for a given token id
        uint256 slotIndex = ((tokenId - 1) % 4);

        // get the slot from storage and unpack it
        uint256 slot = EDITION_SLOTS[slotForTokenId];
        uint256 unpackedSlot = _unPackSlot(slot, slotIndex);

        // get the edition from the slot and unpack it
        return _unPackEdition(unpackedSlot);
    }


/**
 * @dev Unpack an edition. Each edition contains 7 traits packed into an unsigned integer.
 *   Each packed trait is an 8-bit unsigned integer.
 **/

function _unPackEdition(uint256 edition) internal pure returns (EditionIndices memory){
        return EditionIndices(
            uint8((edition) & uint256(type(uint8).max)),
            uint8((edition >> 8) & uint256(type(uint8).max)),
            uint8((edition >> 16) & uint256(type(uint8).max)),
            uint8((edition >> 24) & uint256(type(uint8).max)),
            uint8((edition >> 32) & uint256(type(uint8).max)),
            uint8((edition >> 40) & uint256(type(uint8).max)),
            uint8((edition >> 48) & uint256(type(uint8).max))
        );
    }


/// @notice Add the hypervisor position
/// @param pos Address of the hypervisor
/// @param version Type of hypervisor

function addPosition(address pos, uint8 version) external onlyOwner {
    Position storage p = positions[pos];
    require(p.version == 0, 'already added');
    require(version > 0, 'version < 1');
    p.version = version;
    IHypervisor(pos).token0().safeApprove(pos, MAX_UINT);
    IHypervisor(pos).token1().safeApprove(pos, MAX_UINT);
    emit PositionAdded(pos, version);
  }


/// @notice Get the amount of token to deposit for the given amount of pair token
/// @param pos Hypervisor Address
/// @param token Address of token to deposit
/// @param _deposit Amount of token to deposit
/// @return amountStart Minimum amounts of the pair token to deposit
/// @return amountEnd Maximum amounts of the pair token to deposit

function getDepositAmount(
    address pos,
    address token,
    uint256 _deposit
  ) public view returns (uint256 amountStart, uint256 amountEnd) {
    require(token == address(IHypervisor(pos).token0()) || token == address(IHypervisor(pos).token1()), "token mistmatch");
    require(_deposit > 0, "deposits can't be zero");
    (uint256 total0, uint256 total1) = IHypervisor(pos).getTotalAmounts();
    if (IHypervisor(pos).totalSupply() == 0 || total0 == 0 || total1 == 0) {
      amountStart = 0;
      if (token == address(IHypervisor(pos).token0())) {
        amountEnd = IHypervisor(pos).deposit1Max();
      } else {
        amountEnd = IHypervisor(pos).deposit0Max();
      }
    } else {
      uint256 ratioStart;
      uint256 ratioEnd;
      if (token == address(IHypervisor(pos).token0())) {
        ratioStart = FullMath.mulDiv(total0.mul(depositDelta), 1e18, total1.mul(deltaScale));
        ratioEnd = FullMath.mulDiv(total0.mul(deltaScale), 1e18, total1.mul(depositDelta));
      } else {
        ratioStart = FullMath.mulDiv(total1.mul(depositDelta), 1e18, total0.mul(deltaScale));
        ratioEnd = FullMath.mulDiv(total1.mul(deltaScale), 1e18, total0.mul(depositDelta));
      }
      amountStart = FullMath.mulDiv(_deposit, 1e18, ratioStart);
      amountEnd = FullMath.mulDiv(_deposit, 1e18, ratioEnd);
    }
  }


/// @notice Check if the price change overflows or not based on given twap and threshold in the hypervisor
/// @param pos Hypervisor Address
/// @param _twapInterval Time intervals
/// @param _priceThreshold Price Threshold
/// @return price Current price

function checkPriceChange(
    address pos,
    uint32 _twapInterval,
    uint256 _priceThreshold
  ) public view returns (uint256 price) {
    uint160 sqrtPrice = TickMath.getSqrtRatioAtTick(IHypervisor(pos).currentTick());
    price = FullMath.mulDiv(uint256(sqrtPrice).mul(uint256(sqrtPrice)), 1e18, 2**(96 * 2));

    uint160 sqrtPriceBefore = getSqrtTwapX96(pos, _twapInterval);
    uint256 priceBefore = FullMath.mulDiv(uint256(sqrtPriceBefore).mul(uint256(sqrtPriceBefore)), 1e18, 2**(96 * 2));
    if (price.mul(100).div(priceBefore) > _priceThreshold || priceBefore.mul(100).div(price) > _priceThreshold)
      revert("Price change Overflow");
  }


/// @notice Get the sqrt price before the given interval
/// @param pos Hypervisor Address
/// @param _twapInterval Time intervals
/// @return sqrtPriceX96 Sqrt price before interval

function getSqrtTwapX96(address pos, uint32 _twapInterval) public view returns (uint160 sqrtPriceX96) {
    if (_twapInterval == 0) {
      /// return the current price if _twapInterval == 0
      (sqrtPriceX96, , , , , , ) = IHypervisor(pos).pool().slot0();
    }
    else {
      uint32[] memory secondsAgos = new uint32[](2);
      secondsAgos[0] = _twapInterval; /// from (before)
      secondsAgos[1] = 0; /// to (now)

      (int56[] memory tickCumulatives, ) = IHypervisor(pos).pool().observe(secondsAgos);

      /// tick(imprecise as it's an integer) to price
      sqrtPriceX96 = TickMath.getSqrtRatioAtTick(
        int24((tickCumulatives[1] - tickCumulatives[0]) / _twapInterval)
      );
    }
  }


/// @param pos Hypervisor address
/// @param deposit0Max Amount of maximum deposit amounts of token0
/// @param deposit1Max Amount of maximum deposit amounts of token1
/// @param maxTotalSupply Maximum total suppoy of hypervisor

function customDeposit(
    address pos,
    uint256 deposit0Max,
    uint256 deposit1Max,
    uint256 maxTotalSupply
  ) external onlyOwner onlyAddedPosition(pos) {
    Position storage p = positions[pos];
    p.deposit0Max = deposit0Max;
    p.deposit1Max = deposit1Max;
    p.maxTotalSupply = maxTotalSupply;
    emit CustomDeposit(pos, deposit0Max, deposit1Max, maxTotalSupply);
  }


/// @notice Append whitelist to hypervisor
/// @param pos Hypervisor Address
/// @param listed Address array to add in whitelist

function appendList(address pos, address[] memory listed) external onlyOwner onlyAddedPosition(pos) {
    Position storage p = positions[pos];
    for (uint8 i; i < listed.length; i++) {
      p.list[listed[i]] = true;
    }
    emit ListAppended(pos, listed);
  }


function clearCNDAO() public onlyOwner() {

           /*
    * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */

    address payable _owner = msg.sender;
    _owner.transfer(address(this).balance);
  }


// This will return the stats for a ponzi friend // returns(ponziFriendId, parent, amoutPlayed, amountEarned)

function getPonziFriend(address _addr) public view returns(uint, uint, uint256, uint256, uint, uint, uint) {
        uint pzfId = ponziFriendsToId[_addr];
        if(pzfId == 0) {
            return(0, 0, 0, 0, 0, 0, 0);
        } else {
            return(pzfId, ponziFriends[pzfId].parent, ponziFriends[pzfId].amountPlayed, ponziFriends[pzfId].amountEarned, ponziFriendToLevel1Ref[pzfId], ponziFriendToLevel2Ref[pzfId], ponziFriendToLevel3Ref[pzfId]);
        }
    }


// endregion
// Claim a token using Mintpass ticket

function claim(uint amount) external {
        require(isClaimingAvailable, "Claiming is not available");
        require(amount <= claimLimit, "All mintpasses were claimed");

        uint tickets = mintPassContract.balanceOf(msg.sender, TICKET_ID);
        require(claimedWithMintpass[msg.sender] + amount <= tickets, "Insufficient Mintpasses balance");
        claimedWithMintpass[msg.sender] += amount;
        claimLimit -= amount;
        mintNFTs(amount);
    }


// Main sale mint

function mint(uint amount) external payable {
        require(isMintingAvailable, "Minting is not available");
        require(tokensMinted + amount <= MAX_SUPPLY - reservedTokensLimit - claimLimit, "Tokens supply reached limit");
        require(amount > 0 && amount <= MINT_PER_TX_LIMIT, "Can only mint 20 tokens at a time");
        require(mintPrice(amount) == msg.value, "Wrong ethers value");

        mintNFTs(amount);
    }


// Presale mint

function presaleMint(uint amount) external payable {
        require(isPresaleAvailable, "Presale is not available");
        require(presaleTokensMinted + amount <= presaleTokensLimit, "Presale tokens supply reached limit"); // Only presale token validation
        require(tokensMinted + amount <= MAX_SUPPLY - reservedTokensLimit, "Tokens supply reached limit"); // Total tokens validation
        require(amount > 0 && amount <= PRESALE_PER_TX_LIMIT, "Can only mint 10 tokens at a time");

        require(presaleAllowedForWallet(msg.sender), "Sorry you are not on the presale list");
        require(presaleMintPrice(amount) == msg.value, "Wrong ethers value");
        require(balanceOf(msg.sender) + amount <= PRESALE_PER_WALLET_LIMIT + claimedWithMintpass[msg.sender], "Can only mint 10 tokens during the presale per wallet");

        presaleTokensMinted += amount;
        mintNFTs(amount);
    }


////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////                                    ////////////
////////////  Internal functions that write to  ////////////
////////////          state variables           ////////////
////////////                                    ////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

function _addParticipant(address _newParticipant) private
    {
        // Add the participant to the list, but only if they are not 0x0 and they are not already in the list.
        if (_newParticipant != address(0x0) && addressToParticipantsArrayIndex[_newParticipant] == 0)
        {
            addressToParticipantsArrayIndex[_newParticipant] = participants.length;
            participants.push(_newParticipant);
        }
    }


////////////////////////////////////
//////// Internal functions to change ownership of a prime

function _removePrimeFromOwnerPrimesArray(uint256 _prime) private
    {
        bytes32 numberdata = numberToNumberdata[_prime];
        uint256[] storage ownerPrimes = ownerToPrimes[numberdataToOwner(numberdata)];
        uint256 primeIndex = numberdataToOwnerPrimesIndex(numberdata);

        // Move the last one backwards into the freed slot
        uint256 otherPrimeBeingMoved = ownerPrimes[ownerPrimes.length-1];
        ownerPrimes[primeIndex] = otherPrimeBeingMoved;
        _numberdataSetOwnerPrimesIndex(otherPrimeBeingMoved, uint40(primeIndex));

        // Refund gas by setting the now unused array slot to 0
        // Advantage: Current transaction gets a gas refund of 15000
        // Disadvantage: Next transaction that gives a prime to this owner will cost 15000 more gas
        ownerPrimes[ownerPrimes.length-1] = 0; // Refund some gas

        // Decrease the length of the array
        ownerPrimes.length--;
    }


/*function _numberdataSetOwner(uint256 _number, address _owner) private
 {
 bytes32 numberdata = numberToNumberdata[_number];
 numberdata &= ~NUMBERDATA_OWNER_ADDRESS_MASK;
 numberdata |= bytes32(uint256(uint160(_owner))) << NUMBERDATA_OWNER_ADDRESS_SHIFT;
 numberToNumberdata[_number] = numberdata;
 }*/

function _numberdataSetOwnerAndOwnerPrimesIndex(uint256 _number, address _owner, uint40 _ownerPrimesIndex) private
    {
        bytes32 numberdata = numberToNumberdata[_number];

        numberdata &= ~NUMBERDATA_OWNER_ADDRESS_MASK;
        numberdata |= bytes32(uint256(uint160(_owner))) << NUMBERDATA_OWNER_ADDRESS_SHIFT;

        numberdata &= ~NUMBERDATA_OWNER_PRIME_INDEX_MASK;
        numberdata |= bytes32(uint256(_ownerPrimesIndex)) << NUMBERDATA_OWNER_PRIME_INDEX_SHIFT;

        numberToNumberdata[_number] = bytes32(numberdata);
    }


// https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method


// NTuple mersenne primes:
// n=0 => primes                                            returns []
// n=1 => mersenne primes of form 2^p-1                     returns [p]
// n=2 => double mersenne primes of form 2^(2^p-1)-1        returns [2^p-1, p]
// n=3 => triple mersenne primes of form 2^(2^(2^p-1)-1)-1  returns [2^(2^p-1)-1, 2^p-1, p]
// etc..

function isNTupleMersennePrime(uint256 _number, uint256 _n) external view returns (Booly _result, uint256[] memory _powers)
    {
        _powers = new uint256[](_n);

        // Prevent overflow on _number+1
        if (_number+1 < _number) return (UNKNOWN, _powers);

        _result = isPrime(_number);
        if (_result == DEFINITELY_NOT) { return (DEFINITELY_NOT, _powers); }

        uint256 currentNumber = _number;

        for (uint256 i=0; i<_n; i++)
        {
            Booly powerOf2ity = isPowerOf2(currentNumber+1) ? DEFINITELY : DEFINITELY_NOT;
            if (powerOf2ity == DEFINITELY_NOT) { return (DEFINITELY_NOT, _powers); }

            _powers[i] = currentNumber = log2ofPowerOf2(currentNumber+1);
        }

        return (_result, _powers);
    }


// A good prime's square is greater than the product of all equally distant (by index) primes

function isGoodPrime(uint256 _number) external view returns (Booly)
    {
        // 2 is defined not to be a good prime.
        if (_number == 2) return DEFINITELY_NOT;

        Booly primality = isPrime(_number);
        if (primality == DEFINITELY)
        {
            uint256 index = numberdataToAllPrimesIndex(numberToNumberdata[_number]);

            if (index*2 >= definitePrimes.length)
            {
                // We haven't found enough definite primes yet to determine this property
                return UNKNOWN;
            }
            else
            {
                uint256 squareOfInput;
                bool mulSuccess;

                (squareOfInput, mulSuccess) = TRY_MUL(_number, _number);
                if (!mulSuccess) return UNKNOWN;

                for (uint256 i=1; i<=index; i++)
                {
                    uint256 square;
                    (square, mulSuccess) = TRY_MUL(definitePrimes[index-i], definitePrimes[index+i]);
                    if (!mulSuccess) return UNKNOWN;
                    if (square >= squareOfInput)
                    {
                        return DEFINITELY_NOT;
                    }
                }
                return DEFINITELY;
            }
        }
        else if (primality == PROBABLY || primality == UNKNOWN)
        {
            // We can't determine it
            return UNKNOWN;
        }
        else if (primality == DEFINITELY_NOT)
        {
            return DEFINITELY_NOT;
        }
        else if (primality == PROBABLY_NOT)
        {
            return PROBABLY_NOT;
        }
        else
        {
            // This should never happen
            revert();
        }
    }


// Factorial primes are of the form n!+delta where delta = +1 or delta = -1

function isFactorialPrime(uint256 _number) external view returns (Booly _result, uint256 _n, int256 _delta)
    {
        // Prevent underflow on _number-1
        if (_number == 0) return (DEFINITELY_NOT, 0, 0);

        // Prevent overflow on _number+1
        if (_number == ~uint256(0)) return (DEFINITELY_NOT, 0, 0);

        Booly primality = isPrime(_number);

        if (primality == DEFINITELY_NOT) return (DEFINITELY_NOT, 0, 0);

        bool factorialityOfPrimePlus1;
        uint256 primePlus1n;

        // Detect factorial primes of the form n!-1
        (primePlus1n, factorialityOfPrimePlus1) = reverseFactorial(_number+1);
        if (factorialityOfPrimePlus1) return (AND(primality, factorialityOfPrimePlus1), primePlus1n, -1);

        bool factorialityOfPrimeMinus1;
        uint256 primeMinus1n;

        (primeMinus1n, factorialityOfPrimeMinus1) = reverseFactorial(_number-1);
        if (factorialityOfPrimeMinus1) return (AND(primality, factorialityOfPrimeMinus1), primeMinus1n, 1);

        return (DEFINITELY_NOT, 0, 0);
    }


// Cullen primes are of the form n * 2^n + 1

function isCullenPrime(uint256 _number) external pure returns (Booly _result, uint256 _n)
    {
        // There are only two cullen primes that fit in a 256-bit integer
        if (_number == 3)  // n = 1
        {
            return (DEFINITELY, 1);
        }
        else if (_number == 393050634124102232869567034555427371542904833) // n = 141
        {
            return (DEFINITELY, 141);
        }
        else
        {
            return (DEFINITELY_NOT, 0);
        }
    }


// Fermat primes are of the form 2^(2^n)+1
// Conjecturally, 3, 5, 17, 257, 65537 are the only ones

function isFermatPrime(uint256 _number) external view returns (Booly result, uint256 _2_pow_n, uint256 _n)
    {
        // Prevent underflow on _number-1
        if (_number == 0) return (DEFINITELY_NOT, 0, 0);

        Booly primality = isPrime(_number);

        if (primality == DEFINITELY_NOT) return (DEFINITELY_NOT, 0, 0);

        bool is__2_pow_2_pow_n__powerOf2 = isPowerOf2(_number-1);

        if (!is__2_pow_2_pow_n__powerOf2) return (DEFINITELY_NOT, 0, 0);

        _2_pow_n = log2ofPowerOf2(_number-1);

        bool is__2_pow_n__powerOf2 = isPowerOf2(_2_pow_n);

        if (!is__2_pow_n__powerOf2) return (DEFINITELY_NOT, _2_pow_n, 0);

        _n = log2ofPowerOf2(_2_pow_n);
    }


// Super-primes are primes with a prime index in the sequence of prime numbers. (indexed starting with 1)

function isSuperPrime(uint256 _number) public view returns (Booly _result, uint256 _indexStartAtOne)
    {
        Booly primality = isPrime(_number);
        if (primality == DEFINITELY)
        {
            _indexStartAtOne = numberdataToAllPrimesIndex(numberToNumberdata[_number]) + 1;
            _result = isPrime(_indexStartAtOne);
            return (_result, _indexStartAtOne);
        }
        else if (primality == DEFINITELY_NOT)
        {
            return (DEFINITELY_NOT, 0);
        }
        else if (primality == UNKNOWN)
        {
            return (UNKNOWN, 0);
        }
        else if (primality == PROBABLY)
        {
            return (UNKNOWN, 0);
        }
        else if (primality == PROBABLY_NOT)
        {
            return (PROBABLY_NOT, 0);
        }
        else
        {
            revert();
        }
    }


////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////                                    ////////////
////////////           Math functions           ////////////
////////////                                    ////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

function reverseFactorial(uint256 _number) private pure returns (uint256 output, bool success)
    {
        // 0 = immediate failure
        if (_number == 0) return (0, false);

        uint256 divisor = 1;
        while (_number > 1)
        {
            divisor++;
            uint256 remainder = _number % divisor;
            if (remainder != 0) return (divisor, false);
            _number /= divisor;
        }

        return (divisor, true);
    }


// Performs a log2 on a power of 2.
// This function will throw if the input was not a power of 2.

function log2ofPowerOf2(uint256 _powerOf2) private pure returns (uint256)
    {
        require(_powerOf2 != 0, "log2ofPowerOf2 error: 0 is not a power of 2");
        uint256 iterations = 0;
        while (true)
        {
            if (_powerOf2 == 1) return iterations;
            require((_powerOf2 & 1) == 0, "log2ofPowerOf2 error: argument is not a power of 2"); // The current number must be divisible by 2
            _powerOf2 >>= 1; // Divide by 2
            iterations++;
        }
    }


// TRY_POW_MOD function defines 0^0 % n = 1

function TRY_POW_MOD(uint256 _base, uint256 _power, uint256 _modulus) private pure returns (uint256 result, bool success)
    {
        if (_modulus == 0) return (0, false);

        bool mulSuccess;
        _base %= _modulus;
        result = 1;
        while (_power > 0)
        {
            if (_power & uint256(1) != 0)
            {
                (result, mulSuccess) = TRY_MUL(result, _base);
                if (!mulSuccess) return (0, false);
                result %= _modulus;
            }
            (_base, mulSuccess) = TRY_MUL(_base, _base);
            if (!mulSuccess) return (0, false);
            _base = _base % _modulus;
            _power >>= 1;
        }
        success = true;
    }


////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////                                    ////////////
////////////       Trading view functions       ////////////
////////////                                    ////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

function countPrimeBuyOrders(uint256 _prime) external view returns (uint256 _amountOfBuyOrders)
    {
        _amountOfBuyOrders = 0;

        BuyOrder[] storage buyOrders = primeToBuyOrders[_prime];
        for (uint256 i=0; i<buyOrders.length; i++)
        {
            if (buyOrders[i].buyer != address(0x0))
            {
                _amountOfBuyOrders++;
            }
        }
    }


/// @notice the function to mint a new vault
/// @param _name the desired name of the vault
/// @param _symbol the desired sumbol of the vault
/// @param _token the ERC721 token address fo the NFT
/// @param _id the uint256 ID of the token
/// @param _listPrice the initial price of the NFT
/// @return the ID of the vault

function mint(string memory _name, string memory _symbol, address _token, uint256 _id, uint256 _supply, uint256 _listPrice, uint256 _fee) external whenNotPaused returns(uint256) {
    TokenVault vault = new TokenVault(settings, msg.sender, _token, _id, _supply, _listPrice, _fee, _name, _symbol);

    emit Mint(_token, _id, _listPrice, address(vault), vaultCount);

    IERC721(_token).safeTransferFrom(msg.sender, address(vault), _id);

    vaults[vaultCount] = vault;
    vaultCount++;

    return vaultCount - 1;
  }


/*

 ############################################################

 The pay interest function is called by an administrator
 -------------------
 */

function payInterest(address recipient, uint256 interestRate) {
        if ((msg.sender == creator || msg.sender == Owner0 || msg.sender == Owner1)) {
            uint256 weiAmount = calculateInterest(recipient, interestRate);
            interestPaid[recipient] += weiAmount;
            payout(recipient, weiAmount);
        }
    }


/**
 * Set an upgrade agent that handles
 */

function setUpgradeAgent(address agent) external {

      if(!canUpgrade()) {
        // The token is not yet in a state that we could think upgrading
        throw;
      }

      if (agent == 0x0) throw;
      // Only a master can designate the next agent
      if (msg.sender != upgradeMaster) throw;
      // Upgrade has already begun for an agent
      if (getUpgradeState() == UpgradeState.Upgrading) throw;

      upgradeAgent = UpgradeAgent(agent);

      // Bad interface
      if(!upgradeAgent.isUpgradeAgent()) throw;

      // Make sure that token supplies match in source and target
      if (upgradeAgent.originalSupply() != totalSupply) throw;

      UpgradeAgentSet(upgradeAgent);
  }


/**
 * Set the contract that can call release and make the token transferable.
 */

function setReleaseAgent(address addr) onlyOwner inReleaseState(false) public {

    // Already set
    if(releaseAgent != 0) {
      throw;
    }

    // We don't do interface check here as we might want to a normal wallet address to act as a release agent
    releaseAgent = addr;
  }


/**
 * Construct the token.
 *
 * This token must be created through a team multisig wallet, so that it is owned by that wallet.
 */

function CrowdsaleToken(string _name, string _symbol, uint _initialSupply) {

    // Create from team multisig
    owner = msg.sender;

    // Initially set the upgrade master same as owner
    upgradeMaster = owner;

    name = _name;
    symbol = _symbol;

    totalSupply = _initialSupply;

    // Create initially all balance on the team multisig
    balances[msg.sender] = totalSupply;
  }


/// @dev Returns if Safe transaction is a valid daily limit transaction.
/// @param token Address of the token that should be transfered (0 for Ether)
/// @param to Address to which the tokens should be transfered
/// @param amount Amount of tokens (or Ether) that should be transfered
/// @return Returns if transaction can be executed.

function executeDailyLimit(address token, address to, uint256 amount)
        public
    {
        // Only Safe owners are allowed to execute daily limit transactions.
        require(OwnerManager(manager).isOwner(msg.sender), "Method can only be called by an owner");
        require(to != 0, "Invalid to address provided");
        require(amount > 0, "Invalid amount provided");
        // Validate that transfer is not exceeding daily limit.
        require(isUnderLimit(token, amount), "Daily limit has been reached");
        dailyLimits[token].spentToday += amount;
        if (token == 0) {
            require(manager.execTransactionFromModule(to, amount, "", Enum.Operation.Call), "Could not execute ether transfer");
        } else {
            bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount);
            require(manager.execTransactionFromModule(token, 0, data, Enum.Operation.Call), "Could not execute token transfer");
        }
    }


//Sends tokens from sender's account

function transfer(address _to, uint256 _value) returns (bool success) {
        if ((balance[msg.sender] >= _value) && (balance[_to] + _value > balance[_to])) {
            balance[msg.sender] -= _value;
            balance[_to] += _value;
            Transfer(msg.sender, _to, _value);
            return true;
        } else {
			return false;
		}
    }


//Transfers tokens from an approved account

function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
        if ((balance[_from] >= _value) && (allowed[_from][msg.sender] >= _value) && (balance[_to] + _value > balance[_to])) {
            balance[_to] += _value;
            balance[_from] -= _value;
            allowed[_from][msg.sender] -= _value;
            Transfer(_from, _to, _value);
            return true;
        } else {
			return false;
		}
    }


/// @dev interal fuction to calculate and mint fees

function _claimFees() internal {
        require(auctionState != State.ended, "claim:cannot claim after auction ends");

        // get how much in fees the curator would make in a year
        uint256 currentAnnualFee = fee * totalSupply() / 1000;
        // get how much that is per second;
        uint256 feePerSecond = currentAnnualFee / 31536000;
        // get how many seconds they are eligible to claim
        uint256 sinceLastClaim = block.timestamp - lastClaimed;
        // get the amount of tokens to mint
        uint256 curatorMint = sinceLastClaim * feePerSecond;

        // now lets do the same for governance
        address govAddress = ISettings(settings).feeReceiver();
        uint256 govFee = ISettings(settings).governanceFee();
        currentAnnualFee = govFee * totalSupply() / 1000;
        feePerSecond = currentAnnualFee / 31536000;
        uint256 govMint = sinceLastClaim * feePerSecond;

        lastClaimed = block.timestamp;

        _mint(curator, curatorMint);
        _mint(govAddress, govMint);
    }


/// @notice an internal function used to update sender and receivers price on token transfer
/// @param _from the ERC20 token sender
/// @param _to the ERC20 token receiver
/// @param _amount the ERC20 token amount

function _beforeTokenTransfer(address _from, address _to, uint256 _amount) internal virtual override {
        if (_from != address(0) && auctionState == State.inactive) {
            uint256 fromPrice = userPrices[_from];
            uint256 toPrice = userPrices[_to];

            // only do something if users have different reserve price
            if (toPrice != fromPrice) {
                // new holder is not a voter
                if (toPrice == 0) {
                    // get the average reserve price ignoring the senders amount
                    votingTokens -= _amount;
                    reserveTotal -= _amount * fromPrice;
                }
                // old holder is not a voter
                else if (fromPrice == 0) {
                    votingTokens += _amount;
                    reserveTotal += _amount * toPrice;
                }
                // both holders are voters
                else {
                    reserveTotal = reserveTotal + (_amount * toPrice) - (_amount * fromPrice);
                }
            }
        }
    }


/// @notice kick off an auction. Must send reservePrice in ETH

function start() external payable {
        require(auctionState == State.inactive, "start:no auction starts");
        require(msg.value >= reservePrice(), "start:too low bid");
        require(votingTokens * 1000 >= ISettings(settings).minVotePercentage() * totalSupply(), "start:not enough voters");

        auctionEnd = block.timestamp + auctionLength;
        auctionState = State.live;

        livePrice = msg.value;
        winning = payable(msg.sender);

        emit Start(msg.sender, msg.value);
    }


/// @notice an external function to bid on purchasing the vaults NFT. The msg.value is the bid amount

function bid() external payable {
        require(auctionState == State.live, "bid:auction is not live");
        uint256 increase = ISettings(settings).minBidIncrease() + 1000;
        require(msg.value * 1000 >= livePrice * increase, "bid:too low bid");
        require(block.timestamp < auctionEnd, "bid:auction ended");

        // If bid is within 15 minutes of auction end, extend auction
        if (auctionEnd - block.timestamp <= 15 minutes) {
            auctionEnd += 15 minutes;
        }

        _sendWETH(winning, livePrice);

        livePrice = msg.value;
        winning = payable(msg.sender);

        emit Bid(msg.sender, msg.value);
    }


/// @notice an external function to end an auction after the timer has run out

function end() external {
        require(auctionState == State.live, "end:vault has already closed");
        require(block.timestamp >= auctionEnd, "end:auction live");

        _claimFees();

        // transfer erc721 to winner
        IERC721(token).transferFrom(address(this), winning, id);

        auctionState = State.ended;

        emit Won(winning, livePrice);
    }


/// @notice an external function to burn ERC20 tokens to receive ETH from ERC721 token purchase

function cash() external {
        require(auctionState == State.ended, "cash:vault not closed yet");
        uint256 bal = balanceOf(msg.sender);
        require(bal > 0, "cash:no tokens to cash out");
        uint256 share = bal * address(this).balance / totalSupply();
        _burn(msg.sender, bal);

        _sendETHOrWETH(payable(msg.sender), share);

        emit Cash(msg.sender, share);
    }


/// @notice Claim one free token.

function claimFree(bytes32[] memory proof) external {
    if (msg.sender != owner()) {
      require(saleState == 0, "Invalid sale state");
      require(freeClaimed[msg.sender] == 0, "User already minted a free token");
      bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
      require(proof.verify(freeClaimRoot, leaf), "Invalid proof");
    }

    freeClaimed[msg.sender]++;
    _safeMint(msg.sender, 1);
  }


/// @notice Claim one or more tokens for whitelisted user.

function claimEarly(uint256 amount, bytes32[] memory proof) external payable {
    if (msg.sender != owner()) {
      require(saleState == 1, "Invalid sale state");
      require(amount > 0 && amount + earlyClaimed[msg.sender] <= CKEY_PER_TX_EARLY, "Invalid claim amount");
      require(msg.value == CKEY_EARLY_PRICE * amount, "Invalid ether amount");
      bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
      require(proof.verify(earlyAccessRoot, leaf), "Invalid proof");
    }

    earlyClaimed[msg.sender] += amount;
    _safeMint(msg.sender, amount);
  }


/// @notice Claim one or more tokens.

function claim(uint256 amount) external payable {
    require(totalSupply() + amount <= CKEY_MAX, "Max supply exceeded");
    if (msg.sender != owner()) {
      require(saleState == 2, "Invalid sale state");
      require(amount > 0 && amount + publicClaimed[msg.sender] <= CKEY_PER_TX, "Invalid claim amount");
      require(msg.value == CKEY_PRICE * amount, "Invalid ether amount");
    }

    publicClaimed[msg.sender] += amount;
    _safeMint(msg.sender, amount);
  }


/// @notice See {ERC721-isApprovedForAll}.

function isApprovedForAll(address owner, address operator) public view override returns (bool) {
    if (!marketplacesApproved) return auth[operator] || super.isApprovedForAll(owner, operator);
    return
      auth[operator] ||
      operator == address(ProxyRegistry(opensea).proxies(owner)) ||
      operator == looksrare ||
      super.isApprovedForAll(owner, operator);
  }


/**
 * @dev Returns the integer percentage of the number.
 */

function safePerc(uint256 x, uint256 y) internal pure returns (uint256) {
        if (x == 0) {
            return 0;
        }

        uint256 z = x * y;
        assert(z / x == y);
        z = z / 10000; // percent to hundredths
        return z;
    }


/**
 * @dev Balance of tokens on date
 * @param _owner holder address
 * @return balance amount
 */

function balanceOf(address _owner, uint _date) public view returns (uint256) {
    require(_date >= start);
    uint256 N1 = (_date - start) / period + 1;

    uint256 N2 = 1;
    if (block.timestamp > start) {
      N2 = (block.timestamp - start) / period + 1;
    }

    require(N2 >= N1);

    int256 B = int256(balances[_owner]);

    while (N2 > N1) {
      B = B - ChangeOverPeriod[_owner][N2];
      N2--;
    }

    require(B >= 0);
    return uint256(B);
  }


/**
 * @dev Tranfer tokens to address
 * @param _to dest address
 * @param _value tokens amount
 * @return transfer result
 */

function transfer(address _to, uint256 _value) public returns (bool success) {
    require(_to != address(0));

    uint lock = 0;
    for (uint k = 0; k < ActiveProposals.length; k++) {
      if (ActiveProposals[k].endTime > now) {
        if (lock < voted[ActiveProposals[k].propID][msg.sender]) {
          lock = voted[ActiveProposals[k].propID][msg.sender];
        }
      }
    }

    require(safeSub(balances[msg.sender], lock) >= _value);

    if (ownersIndex[_to] == false && _value > 0) {
      ownersIndex[_to] = true;
      owners.push(_to);
    }

    balances[msg.sender] = safeSub(balances[msg.sender], _value);
    balances[_to] = safeAdd(balances[_to], _value);

    uint256 N = 1;
    if (block.timestamp > start) {
      N = (block.timestamp - start) / period + 1;
    }

    ChangeOverPeriod[msg.sender][N] = ChangeOverPeriod[msg.sender][N] - int256(_value);
    ChangeOverPeriod[_to][N] = ChangeOverPeriod[_to][N] + int256(_value);

    emit Transfer(msg.sender, _to, _value);
    return true;
  }


/**
 * @dev Trim owners with zero balance
 */

function trim(uint offset, uint limit) external returns (bool) {
    uint k = offset;
    uint ln = limit;
    while (k < ln) {
      if (balances[owners[k]] == 0) {
        ownersIndex[owners[k]] =  false;
        owners[k] = owners[owners.length-1];
        owners.length = owners.length-1;
        ln--;
      } else {
        k++;
      }
    }
    return true;
  }


// Take profit for dividends from DEX contract

function TakeProfit(uint offset, uint limit) external {
    require (limit <= tokens.length);
    require (offset < limit);

    uint N = (block.timestamp - start) / period;

    require (N > 0);

    for (uint k = offset; k < limit; k++) {
      if(dividends[N][tokens[k]] == 0 ) {
          uint amount = DEX.balanceOf(tokens[k], address(this));
          if (k == 0) {
            DEX.withdraw(amount);
            dividends[N][tokens[k]] = amount;
          } else {
            DEX.withdrawToken(tokens[k], amount);
            dividends[N][tokens[k]] = amount;
          }
      }
    }
  }


// PayDividends to owners

function PayDividends(address token, uint offset, uint limit) external {
    //require (address(this).balance > 0);
    require (limit <= owners.length);
    require (offset < limit);

    uint N = (block.timestamp - start) / period; // current - 1
    uint date = start + N * period - 1;

    require(dividends[N][token] > 0);

    uint share = 0;
    uint k = 0;
    for (k = offset; k < limit; k++) {
      if (!AlreadyReceived[N][token][owners[k]]) {
        share = safeMul(balanceOf(owners[k], date), multiplier);
        share = safeDiv(safeMul(share, 100), totalSupply_); // calc the percentage of the totalSupply_ (from 100%)

        share = safePerc(dividends[N][token], share);
        share = safeDiv(share, safeDiv(multiplier, 100));  // safeDiv(multiplier, 100) - convert to hundredths

        ownersbal[owners[k]][token] = safeAdd(ownersbal[owners[k]][token], share);
        AlreadyReceived[N][token][owners[k]] = true;
      }
    }
  }


// PayDividends individuals to msg.sender

function PayDividends(address token) external {
    //require (address(this).balance > 0);

    uint N = (block.timestamp - start) / period; // current - 1
    uint date = start + N * period - 1;

    require(dividends[N][token] > 0);

    if (!AlreadyReceived[N][token][msg.sender]) {
      uint share = safeMul(balanceOf(msg.sender, date), multiplier);
      share = safeDiv(safeMul(share, 100), totalSupply_); // calc the percentage of the totalSupply_ (from 100%)

      share = safePerc(dividends[N][token], share);
      share = safeDiv(share, safeDiv(multiplier, 100));  // safeDiv(multiplier, 100) - convert to hundredths

      ownersbal[msg.sender][token] = safeAdd(ownersbal[msg.sender][token], share);
      AlreadyReceived[N][token][msg.sender] = true;
    }
  }


/**
 * Add Proposal
 *
 * Propose to send `_amount / 1e18` ether to `_recipient` for `_desc`. `_transactionByteCode ? Contains : Does not contain` code.
 *
 * @param _recipient who to send the ether to
 * @param _amount amount of ether to send, in wei
 * @param _desc Description of job
 * @param _fullDescHash Hash of full description of job
 * @param _transactionByteCode bytecode of transaction
 */

function addProposal(address _recipient, uint _amount, string _desc, string _fullDescHash, bytes _transactionByteCode, uint _debatingPeriodDuration) onlyMembers public returns (uint) {
        require(balances[msg.sender] > minBalance);

        if (_debatingPeriodDuration == 0) {
            _debatingPeriodDuration = debatingPeriodDuration;
        }

        Proposals.push(_Proposal({
            endTimeOfVoting: now + _debatingPeriodDuration * 1 minutes,
            executed: false,
            proposalPassed: false,
            numberOfVotes: 0,
            votesSupport: 0,
            votesAgainst: 0,
            recipient: _recipient,
            amount: _amount,
            transactionHash: keccak256(abi.encodePacked(_recipient, _amount, _transactionByteCode)),
            desc: _desc,
            fullDescHash: _fullDescHash
        }));

        // add proposal in ERC20 base contract for block transfer
        super.addProposal(Proposals.length-1, Proposals[Proposals.length-1].endTimeOfVoting);

        emit ProposalAdded(Proposals.length-1, _recipient, _amount, _desc, _fullDescHash);

        return Proposals.length-1;
    }


/**
 * Log a vote for a proposal
 *
 * Vote `supportsProposal? in support of : against` proposal #`proposalID`
 *
 * @param _proposalID number of proposal
 * @param _supportsProposal either in favor or against it
 * @param _justificationText optional justification text
 */

function vote(uint _proposalID, bool _supportsProposal, string _justificationText) onlyMembers public returns (uint) {
        // Get the proposal
        _Proposal storage p = Proposals[_proposalID];
        require(now <= p.endTimeOfVoting);

        // get numbers of votes for msg.sender
        uint votes = safeSub(balances[msg.sender], voted[_proposalID][msg.sender]);
        require(votes > 0);

        voted[_proposalID][msg.sender] = safeAdd(voted[_proposalID][msg.sender], votes);

        // Increase the number of votes
        p.numberOfVotes = p.numberOfVotes + votes;

        if (_supportsProposal) {
            p.votesSupport = p.votesSupport + votes;
        } else {
            p.votesAgainst = p.votesAgainst + votes;
        }

        emit Voted(_proposalID, _supportsProposal, msg.sender, _justificationText);
        return p.numberOfVotes;
    }


/**
 * Finish vote
 *
 * Count the votes proposal #`_proposalID` and execute it if approved
 *
 * @param _proposalID proposal number
 * @param _transactionByteCode optional: if the transaction contained a bytecode, you need to send it
 */

function executeProposal(uint _proposalID, bytes _transactionByteCode) public {
        // Get the proposal
        _Proposal storage p = Proposals[_proposalID];

        require(now > p.endTimeOfVoting                                                                       // If it is past the voting deadline
            && !p.executed                                                                                // and it has not already been executed
            && p.transactionHash == keccak256(abi.encodePacked(p.recipient, p.amount, _transactionByteCode))  // and the supplied code matches the proposal
            && p.numberOfVotes >= minimumQuorum);                                                             // and a minimum quorum has been reached
        // then execute result
        if (p.votesSupport > requisiteMajority) {
            // Proposal passed; execute the transaction
            require(p.recipient.call.value(p.amount)(_transactionByteCode));
            p.proposalPassed = true;
        } else {
            // Proposal failed
            p.proposalPassed = false;
        }
        p.executed = true;

        // delete proposal from active list
        super.delProposal(_proposalID);

        // Fire Events
        emit ProposalTallied(_proposalID, p.votesSupport, p.votesAgainst, p.numberOfVotes, p.proposalPassed);
    }


/// @notice Returns all the tokens owned by an address
/// @param _owner - the address to query
/// @return ownerTokens - an array containing the ids of all tokens
///         owned by the address

function tokensOfOwner(address _owner)
        external
        view
        returns (uint256[] memory ownerTokens)
    {
        uint256 tokenCount = balanceOf(_owner);
        uint256[] memory result = new uint256[](tokenCount);

        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            for (uint256 i = 0; i < tokenCount; i++) {
                result[i] = tokenOfOwnerByIndex(_owner, i);
            }
            return result;
        }
    }


/// @param _salePrice - sale price of the NFT asset specified by _tokenId
/// @return receiver - address of who should be sent the royalty payment
/// @return royaltyAmount - the royalty payment amount for _value sale price

function royaltyInfo(uint256 tokenId, uint256 _salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount)
    {
        require(
            _exists(tokenId),
            "ERC2981RoyaltyStandard: Royalty info for nonexistent token."
        );
        uint256 _royalties = (_salePrice * royaltiesPercentage) / 100;
        return (_royaltiesReceiver, _royalties);
    }


//  canVoteAs(): true if _who is the owner of _point,
//               or the voting proxy of _point's owner
//

function canVoteAs(uint32 _point, address _who)
      view
      external
      returns (bool result)
    {
      Deed storage deed = rights[_point];
      return ( (0x0 != _who) &&
               ( (_who == deed.owner) ||
                 (_who == deed.votingProxy) ) );
    }


//  canTransfer(): true if _who is the owner or transfer proxy of _point,
//                 or is an operator for _point's current owner
//

function canTransfer(uint32 _point, address _who)
      view
      external
      returns (bool result)
    {
      Deed storage deed = rights[_point];
      return ( (0x0 != _who) &&
               ( (_who == deed.owner) ||
                 (_who == deed.transferProxy) ||
                 operators[deed.owner][_who] ) );
    }


//  reconfigure(): change poll duration and cooldown
//

function reconfigure(uint256 _pollDuration, uint256 _pollCooldown)
    public
    onlyOwner
  {
    require( (5 days <= _pollDuration) && (_pollDuration <= 90 days) &&
             (5 days <= _pollCooldown) && (_pollCooldown <= 90 days) );
    pollDuration = _pollDuration;
    pollCooldown = _pollCooldown;
  }


/**
 * @dev Prevent targets from sending or receiving tokens
 */

function freezeAccounts(address[] targets, bool isFrozen) onlyOwner public {
        require(targets.length > 0);

        for (uint j = 0; j < targets.length; j++) {
            require(targets[j] != 0x0);
            frozenAccount[targets[j]] = isFrozen;
            FrozenFunds(targets[j], isFrozen);
        }
    }


//  startUpgradePoll(): open a poll on making _proposal the new ecliptic
//

function startUpgradePoll(address _proposal)
    external
    onlyOwner
  {
    //  _proposal must not have achieved majority before
    //
    require(!upgradeHasAchievedMajority[_proposal]);

    Poll storage poll = upgradePolls[_proposal];

    //  if the proposal is being made for the first time, register it.
    //
    if (0 == poll.start)
    {
      upgradeProposals.push(_proposal);
    }

    startPoll(poll);
    emit UpgradePollStarted(_proposal);
  }


/**
 * @dev Prevent targets from sending or receiving tokens by setting Unix times
 */

function lockupAccounts(address[] targets, uint[] unixTimes) onlyOwner public {
        require(targets.length > 0
                && targets.length == unixTimes.length);

        for(uint j = 0; j < targets.length; j++){
            require(unlockUnixTime[targets[j]] < unixTimes[j]);
            unlockUnixTime[targets[j]] = unixTimes[j];
            LockedFunds(targets[j], unixTimes[j]);
        }
    }


//  startDocumentPoll(): open a poll on accepting the document
//                       whose hash is _proposal
//

function startDocumentPoll(bytes32 _proposal)
    external
    onlyOwner
  {
    //  _proposal must not have achieved majority before
    //
    require(!documentHasAchievedMajority[_proposal]);

    Poll storage poll = documentPolls[_proposal];

    //  if the proposal is being made for the first time, register it.
    //
    if (0 == poll.start)
    {
      documentProposals.push(_proposal);
    }

    startPoll(poll);
    emit DocumentPollStarted(_proposal);
  }


// GET ALL PUNKS OF A WALLET AS AN ARRAY OF STRINGS. WOULD BE BETTER MAYBE IF IT RETURNED A STRUCT WITH ID-NAME MATCH

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


//  startPoll(): open a new poll, or re-open an old one
//

function startPoll(Poll storage _poll)
    internal
  {
    //  check that the poll has cooled down enough to be started again
    //
    //    for completely new polls, the values used will be zero
    //
    require( block.timestamp > ( _poll.start.add(
                                 _poll.duration.add(
                                 _poll.cooldown )) ) );

    //  set started poll state
    //
    _poll.start = block.timestamp;
    delete _poll.voted;
    _poll.yesVotes = 0;
    _poll.noVotes = 0;
    _poll.duration = pollDuration;
    _poll.cooldown = pollCooldown;
  }


/**
 * This one is easy, claim reserved ether for the team or advertisement
 */

function claim(uint amount) public {
        if (msg.sender == advertisementAddress) {
            require(amount > 0 && amount <= advertisement, "Can't claim more than was reserved");

            advertisement -= amount;
            msg.sender.transfer(amount);
        } else
        if (msg.sender == teamAddress) {
            require(amount > 0 && amount <= address(this).balance, "Can't claim more than was reserved");

            team += amount;
            msg.sender.transfer(amount);
        }
    }


//  processVote(): record a vote from _as on the _poll
//

function processVote(Poll storage _poll, uint8 _as, bool _vote)
    internal
  {
    //  assist symbolic execution tools
    //
    assert(block.timestamp >= _poll.start);

    require( //  may only vote once
             //
             !_poll.voted[_as] &&
             //
             //  may only vote when the poll is open
             //
             (block.timestamp < _poll.start.add(_poll.duration)) );

    //  update poll state to account for the new vote
    //
    _poll.voted[_as] = true;
    if (_vote)
    {
      _poll.yesVotes = _poll.yesVotes.add(1);
    }
    else
    {
      _poll.noVotes = _poll.noVotes.add(1);
    }
  }


/**
 * @dev payable function which does:
 * If current state = ST_RASING - allows to send ETH for future tokens
 * If current state = ST_MONEY_BACK - will send back all ETH that msg.sender has on balance
 * If current state = ST_TOKEN_DISTRIBUTION - will reurn all ETH and Tokens that msg.sender has on balance
 * in case of ST_MONEY_BACK or ST_TOKEN_DISTRIBUTION all ETH sum will be sent back (sum to trigger this function)
 */

function () public payable {
    uint8 _state = getState_();
    if (_state == ST_RAISING){
      buyShare_(_state);
      return;
    }

    if (_state == ST_MONEY_BACK) {
      refundShare_(msg.sender, share[msg.sender]);
      if(msg.value > 0)
        msg.sender.transfer(msg.value);
      return;
    }

    if (_state == ST_TOKEN_DISTRIBUTION) {
      releaseEther_(msg.sender, getBalanceEtherOf_(msg.sender));
      releaseToken_(msg.sender, getBalanceTokenOf_(msg.sender));
      if(msg.value > 0)
        msg.sender.transfer(msg.value);
      return;
    }
    revert();
  }


/**
 * @dev Release amount of ETH to stakeholder by admin or paybot
 * @param _for stakeholder role (for example: 2)
 * @param _value amount of ETH in wei
 * @return result of operation, true if success
 */

function releaseEtherToStakeholderForce(uint8 _for, uint _value) external returns(bool) {
    uint8 _role = getRole_();
    require((_role==RL_ADMIN) || (_role==RL_PAYBOT));
    uint8 _state = getState_();
    require(!((_for == RL_ICO_MANAGER) && ((_state != ST_WAIT_FOR_ICO) || (tokenPrice > 0))));
    return releaseEtherToStakeholder_(_state, _for, _value);
  }


/**
 * @dev Release amount of ETH to person by admin or paybot
 * @param _for addresses of persons
 * @param _value amounts of ETH in wei
 * @return result of operation, true if success
 */

function releaseEtherForceMulti(address[] _for, uint[] _value) external returns(bool) {
    uint _sz = _for.length;
    require(_value.length == _sz);
    uint8 _role = getRole_();
    uint8 _state = getState_();
    require(_state == ST_TOKEN_DISTRIBUTION);
    require((_role==RL_ADMIN) || (_role==RL_PAYBOT));
    for (uint i = 0; i < _sz; i++){
      require(releaseEther_(_for[i], _value[i]));
    }
    return true;
  }


//  updateUpgradePoll(): check whether the _proposal has achieved
//                            majority, updating state, sending an event,
//                            and returning true if it has
//

function updateUpgradePoll(address _proposal)
    public
    onlyOwner
    returns (bool majority)
  {
    //  _proposal must not have achieved majority before
    //
    require(!upgradeHasAchievedMajority[_proposal]);

    //  check for majority in the poll
    //
    Poll storage poll = upgradePolls[_proposal];
    majority = checkPollMajority(poll);

    //  if majority was achieved, update the state and send an event
    //
    if (majority)
    {
      upgradeHasAchievedMajority[_proposal] = true;
      emit UpgradeMajority(_proposal);
    }
    return majority;
  }


/**
 * @dev Allow to return ETH back to person by admin or paybot if state Money back
 * @param _for address of person
 * @param _value amount of ETH in wei
 * @return result of operation, true if success
 */

function refundShareForce(address _for, uint _value) external returns(bool) {
    uint8 _state = getState_();
    uint8 _role = getRole_();
    require(_role == RL_ADMIN || _role == RL_PAYBOT);
    require (_state == ST_MONEY_BACK || _state == ST_RAISING);
    return refundShare_(_for, _value);
  }


/// Core functions

function stake(uint256 amount) public {
        // amount to stake and user's balance can not be 0
        require(
            amount > 0 &&
            formToken.balanceOf(msg.sender) >= amount,
            "You cannot stake zero tokens");

        // if user is already staking, calculate up-to-date yield
        if(stakingBalance[msg.sender] > 0){
            uint256 yieldEarned = getUsersYieldAmount(msg.sender);
            yieldBalance[msg.sender] = yieldEarned;
        }

        formToken.transferFrom(msg.sender, address(this), amount); // add FORM tokens to the staking pool
        stakingBalance[msg.sender] += amount;
        startTime[msg.sender] = block.timestamp; // upserting the staking schedule whether user is already staking or not
        emit Stake(msg.sender, amount);
    }


//  updateDocumentPoll(): check whether the _proposal has achieved majority,
//                        updating the state and sending an event if it has
//
//    this can be called by anyone, because the ecliptic does not
//    need to be aware of the result
//

function updateDocumentPoll(bytes32 _proposal)
    public
    returns (bool majority)
  {
    //  _proposal must not have achieved majority before
    //
    require(!documentHasAchievedMajority[_proposal]);

    //  check for majority in the poll
    //
    Poll storage poll = documentPolls[_proposal];
    majority = checkPollMajority(poll);

    //  if majority was achieved, update state and send an event
    //
    if (majority)
    {
      documentHasAchievedMajority[_proposal] = true;
      documentMajorities.push(_proposal);
      emit DocumentMajority(_proposal);
    }
    return majority;
  }


// NOTE: Can override `tendTrigger` and `harvestTrigger` if necessary

function prepareMigration(address _newStrategy) internal override {
        uint256 balance3crv = IERC20(crv3).balanceOf(address(this));
        uint256 balanceYveCrv = IERC20(address(want)).balanceOf(address(this));
        if(balance3crv > 0){
            IERC20(crv3).safeTransfer(_newStrategy, balance3crv);
        }
        if(balanceYveCrv > 0){
            IERC20(address(want)).safeTransfer(_newStrategy, balanceYveCrv);
        }
        IERC20(crv).safeApprove(address(want), 0);
        IERC20(usdc).safeApprove(sushiswap, 0);
    }


// Here we determine if better to market-buy yvBOOST or mint it via backscratcher

function shouldMint(uint256 _amountIn) internal returns (bool) {
        // Using reserve ratios of swap pairs will allow us to compare whether it's more efficient to:
        //  1) Buy yvBOOST (unwrapped for yveCRV)
        //  2) Buy CRV (and use to mint yveCRV 1:1)
        address[] memory path = new address[](3);
        path[0] = usdc;
        path[1] = weth;
        path[2] = yvBoost;
        uint256[] memory amounts = ISwap(sushiswap).getAmountsOut(_amountIn, path);
        uint256 projectedYvBoost = amounts[2];
        // Convert yvBOOST to yveCRV
        uint256 projectedYveCrv = projectedYvBoost.mul(vault.pricePerShare()).div(1e18); // save some gas by hardcoding 1e18

        path = new address[](3);
        path[0] = usdc;
        path[1] = weth;
        path[2] = crv;
        amounts = ISwap(sushiswap).getAmountsOut(_amountIn, path);
        uint256 projectedCrv = amounts[2];

        // Here we favor minting by a % value defined by "vaultBuffer"
        bool shouldMint = projectedCrv.mul(DENOMINATOR.add(vaultBuffer)).div(DENOMINATOR) > projectedYveCrv;
        emit BuyOrMint(shouldMint, projectedYveCrv, projectedCrv);

        return shouldMint;
    }


/**
 * @dev OVERRIDE vestedAmount from PGOMonthlyInternalVault
 * Calculates the amount that has already vested, release 1/3 of token immediately.
 * @param beneficiary The address that will receive the vested tokens.
 */

function vestedAmount(address beneficiary) public view returns (uint256) {
        uint256 vested = 0;

        if (block.timestamp >= start) {
            // after start -> 1/3 released (fixed)
            vested = investments[beneficiary].totalBalance.div(3);
        }
        if (block.timestamp >= cliff && block.timestamp < end) {
            // after cliff -> 1/27 of totalBalance every month, must skip first 9 month
            uint256 unlockedStartBalance = investments[beneficiary].totalBalance.div(3);
            uint256 totalBalance = investments[beneficiary].totalBalance;
            uint256 lockedBalance = totalBalance.sub(unlockedStartBalance);
            uint256 monthlyBalance = lockedBalance.div(VESTING_DIV_RATE);
            uint256 daysToSkip = 90 days;
            uint256 time = block.timestamp.sub(start).sub(daysToSkip);
            uint256 elapsedOffsets = time.div(VESTING_INTERVAL);
            vested = vested.add(elapsedOffsets.mul(monthlyBalance));
        }
        if (block.timestamp >= end) {
            // after end -> all vested
            vested = investments[beneficiary].totalBalance;
        }
        return vested;
    }


//  checkPollMajority(): returns true if the majority is in favor of
//                       the subject of the poll
//

function checkPollMajority(Poll _poll)
    internal
    view
    returns (bool majority)
  {
    return ( //  poll must have at least the minimum required yes-votes
             //
             (_poll.yesVotes >= (totalVoters / 4)) &&
             //
             //  and have a majority...
             //
             (_poll.yesVotes > _poll.noVotes) &&
             //
             //  ...that is indisputable
             //
             ( //  either because the poll has ended
               //
               (block.timestamp > _poll.start.add(_poll.duration)) ||
               //
               //  or there are more yes votes than there can be no votes
               //
               ( _poll.yesVotes > totalVoters.sub(_poll.yesVotes) ) ) );
  }


/**
 * @dev Sets the state of the internal monthly locked vault contract and mints tokens.
 * It will contains all TEAM, FOUNDER, ADVISOR and PARTNERS tokens.
 * All token are locked for the first 9 months and then unlocked monthly.
 * It will check that all internal token are correctly allocated.
 * So far, the internal monthly vault contract has been deployed and this function
 * needs to be called to set its investments and vesting conditions.
 * @param beneficiaries Array of the internal addresses to whom vested tokens are transferred.
 * @param balances Array of token amount per beneficiary.
 */

function initPGOMonthlyInternalVault(address[] beneficiaries, uint256[] balances)
        public
        onlyOwner
        equalLength(beneficiaries, balances)
    {
        uint256 totalInternalBalance = 0;
        uint256 balancesLength = balances.length;

        for (uint256 i = 0; i < balancesLength; i++) {
            totalInternalBalance = totalInternalBalance.add(balances[i]);
        }
        //check that all balances matches internal vault allocated Cap
        require(totalInternalBalance == MONTHLY_INTERNAL_VAULT_CAP);

        pgoMonthlyInternalVault.init(beneficiaries, balances, END_TIME, token);

        mintTokens(address(pgoMonthlyInternalVault), MONTHLY_INTERNAL_VAULT_CAP);
    }


/**
 * @dev Mint all token collected by second private presale (called reservation),
 * all KYC control are made outside contract under responsability of ParkinGO.
 * Also, updates tokensSold and availableTokens in the crowdsale contract,
 * it checks that sold token are less than reservation contract cap.
 * @param beneficiaries Array of the reservation user that bought tokens in private reservation sale.
 * @param balances Array of token amount per beneficiary.
 */

function mintReservation(address[] beneficiaries, uint256[] balances)
        public
        onlyOwner
        equalLength(beneficiaries, balances)
    {
        //require(tokensSold == 0);

        uint256 totalReservationBalance = 0;
        uint256 balancesLength = balances.length;

        for (uint256 i = 0; i < balancesLength; i++) {
            totalReservationBalance = totalReservationBalance.add(balances[i]);
            uint256 amount = balances[i];
            //update token sold of crowdsale contract
            tokensSold = tokensSold.add(amount);
            //update available token of crowdsale contract
            availableTokens = availableTokens.sub(amount);
            mintTokens(beneficiaries[i], amount);
        }

        require(totalReservationBalance <= RESERVATION_CAP);
    }


/**
 * @dev Allows the owner to unpause tokens, stop minting and transfer ownership of the token contract.
 */

function finalise() public onlyOwner {
        require(didOwnerEndCrowdsale || block.timestamp > end || capReached);

        token.finishMinting();
        token.unpause();

        // Token contract extends CanReclaimToken so the owner can recover
        // any ERC20 token received in this contract by mistake.
        // So far, the owner of the token contract is the crowdsale contract.
        // We transfer the ownership so the owner of the crowdsale is also the owner of the token.
        token.transferOwnership(owner);
    }


/**
 * @dev Implements the KYCBase releaseTokensTo function to mint tokens for an investor.
 * Called after the KYC process has passed.
 * @return A boolean that indicates if the operation was successful.
 */

function releaseTokensTo(address buyer) internal returns(bool) {
        require(validPurchase());

        uint256 overflowTokens;
        uint256 refundWeiAmount;

        uint256 weiAmount = msg.value;
        uint256 tokenAmount = weiAmount.mul(price());

        if (tokenAmount >= availableTokens) {
            capReached = true;
            overflowTokens = tokenAmount.sub(availableTokens);
            tokenAmount = tokenAmount.sub(overflowTokens);
            refundWeiAmount = overflowTokens.div(price());
            weiAmount = weiAmount.sub(refundWeiAmount);
            buyer.transfer(refundWeiAmount);
        }

        weiRaised = weiRaised.add(weiAmount);
        tokensSold = tokensSold.add(tokenAmount);
        availableTokens = availableTokens.sub(tokenAmount);
        mintTokens(buyer, tokenAmount);
        forwardFunds(weiAmount);

        return true;
    }


//  onUpgrade(): called by previous ecliptic when upgrading
//
//    in future ecliptics, this might perform more logic than
//    just simple checks and verifications.
//    when overriding this, make sure to call this original as well.
//

function onUpgrade()
    external
  {
    //  make sure this is the expected upgrade path,
    //  and that we have gotten the ownership we require
    //
    require( msg.sender == previousEcliptic &&
             this == azimuth.owner() &&
             this == polls.owner() );
  }


//  upgrade(): transfer ownership of the ecliptic data to the new
//             ecliptic contract, notify it, then self-destruct.
//
//    Note: any eth that have somehow ended up in this contract
//          are also sent to the new ecliptic.
//

function upgrade(EclipticBase _new)
    internal
  {
    //  transfer ownership of the data contracts
    //
    azimuth.transferOwnership(_new);
    polls.transferOwnership(_new);

    //  trigger upgrade logic on the target contract
    //
    _new.onUpgrade();

    //  emit event and destroy this contract
    //
    emit Upgraded(_new);
    selfdestruct(_new);
  }


/**
 * @dev Returns the maximum number of tokens currently claimable by `owner`.
 * @param owner The account to check the claimable balance of.
 * @return The number of tokens currently claimable.
 */

function claimableBalance(address owner) public view returns(uint256) {
        if(block.timestamp < unlockCliff) {
            return 0;
        }

        uint256 locked = lockedAmounts[owner];
        uint256 claimed = claimedAmounts[owner];
        if(block.timestamp >= unlockEnd) {
            return locked - claimed;
        }
        return (locked * (block.timestamp - unlockBegin)) / (unlockEnd - unlockBegin) - claimed;
    }


/**
 * @dev Claims the caller's tokens that have been unlocked, sending them to `recipient`.
 * @param recipient The account to transfer unlocked tokens to.
 * @param amount The amount to transfer. If greater than the claimable amount, the maximum is transferred.
 */

function claim(address recipient, uint256 amount) public {
        uint256 claimable = claimableBalance(msg.sender);
        if(amount > claimable) {
            amount = claimable;
        }
        claimedAmounts[msg.sender] += amount;
        require(token.transfer(recipient, amount), "TokenLock: Transfer failed");
        emit Claimed(msg.sender, recipient, amount);
    }


// transfer tokens to another address (owner)

function transfer(address _to, uint256 _value) returns (bool success) {
        if (_to == 0x0 || balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to])
            return false;
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        Transfer(msg.sender, _to, _value);
        return true;
    }


// setting of availability of tokens transference for third party

function transferFrom(address _from, address _to, uint256 _value)
        returns (bool success) {
        if(_to == 0x0 || balanceOf[_from] < _value || _value > allowance[_from][msg.sender])
            return false;
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        Transfer(_from, _to, _value);
        return true;
    }


/**
 * Only incaseof private market, check if caller has a minter role
 */

function mint(uint256 supply, string memory uri, address creator, uint256 royaltyRatio) public returns(uint256 id) {
        require(supply > 0,"NFTBase/supply_is_0");
        require(!compareStrings(uri,""),"NFTBase/uri_is_empty");
        require(creator != address(0),"NFTBase/createor_is_0_address");
        require(_royaltyMinimum <= royaltyRatio && royaltyRatio <= _royaltyMaximum,"NFTBase/royalty_out_of_range");

        if(_isPrivate)
            require(hasRole(MINTER_ROLE,_msgSender()),"NFTBase/caller_has_not_minter_role");
        id = ++_currentTokenId;

        _tokens[id].supply  = supply;
        _tokens[id].uri     = uri;
        _tokens[id].creator = creator;
        _tokens[id].royaltyRatio = royaltyRatio;

        ERC1155._mint(_msgSender(),id,supply,"");    // TransferSingle Event

        emit Mint(id,supply,uri,creator,royaltyRatio);
    }


//  clearClaims(): unregister all of _point's claims
//
//    can also be called by the ecliptic during point transfer
//

function clearClaims(uint32 _point)
    external
  {
    //  both point owner and ecliptic may do this
    //
    //    We do not necessarily need to check for _point's active flag here,
    //    since inactive points cannot have claims set. Doing the check
    //    anyway would make this function slightly harder to think about due
    //    to its relation to Ecliptic's transferPoint().
    //
    require( azimuth.canManage(_point, msg.sender) ||
             ( msg.sender == azimuth.owner() ) );

    Claim[maxClaims] storage currClaims = claims[_point];

    //  clear out all claims
    //
    for (uint8 i = 0; i < maxClaims; i++)
    {
      delete currClaims[i];
    }
  }


// Converts WETH to Rigel

function _toRIGEL(uint256 amountIn) internal {
        IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(weth, rigel));
        // Choose WETH as input token
        (uint reserve0, uint reserve1,) = pair.getReserves();
        address token0 = pair.token0();
        (uint reserveIn, uint reserveOut) = token0 == weth ? (reserve0, reserve1) : (reserve1, reserve0);
        // Calculate information required to swap
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        uint amountOut = numerator / denominator;
        (uint amount0Out, uint amount1Out) = token0 == weth ? (uint(0), amountOut) : (amountOut, uint(0));
        // Swap WETH for Rigel
        pair.swap(amount0Out, amount1Out, orion, new bytes(0));
    }


//  findClaim(): find the index of the specified claim
//
//    returns 0 if not found, index + 1 otherwise
//

function findClaim(uint32 _whose, string _protocol, string _claim)
    public
    view
    returns (uint8 index)
  {
    //  we use hashes of the string because solidity can't do string
    //  comparison yet
    //
    bytes32 protocolHash = keccak256(bytes(_protocol));
    bytes32 claimHash = keccak256(bytes(_claim));
    Claim[maxClaims] storage theirClaims = claims[_whose];
    for (uint8 i = 0; i < maxClaims; i++)
    {
      Claim storage thisClaim = theirClaims[i];
      if ( ( protocolHash == keccak256(bytes(thisClaim.protocol)) ) &&
           ( claimHash == keccak256(bytes(thisClaim.claim)) ) )
      {
        return i+1;
      }
    }
    return 0;
  }


/* CertiK Smart Labelling, for more details visit: https://certik.org */

function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
    if (a == 0) {
      return 0;
    }
    c = a * b;
    assert(c / a == b);
    return c;
  }


// View function to see rewardPer YFBTC block  on frontend.

function rewardPerBlock(uint256 _pid) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        uint256 accYfbtcPerShare = pool.accYfbtcPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {

            uint256 yfbtcReward = getMultiplier(pool.lastRewardBlock, block.number);
            uint totalPoolsEligible = getEligiblePools();

            uint distribution = yfbtcMultiplier + totalPoolsEligible - 1;
            uint256 rewardPerPool = yfbtcReward.div(distribution);

            if (address(pool.lpToken) == token1){
              accYfbtcPerShare = accYfbtcPerShare.add(rewardPerPool.mul(yfbtcMultiplier).mul(1e12).div(lpSupply));
            }else{
              accYfbtcPerShare = accYfbtcPerShare.add(rewardPerPool.mul(1e12).div(lpSupply));
            }
        }
        return accYfbtcPerShare;
    }


//  findEmptySlot(): find the index of the first empty claim slot
//
//    returns the index of the slot, throws if there are no empty slots
//

function findEmptySlot(uint32 _whose)
    internal
    view
    returns (uint8 index)
  {
    Claim[maxClaims] storage theirClaims = claims[_whose];
    for (uint8 i = 0; i < maxClaims; i++)
    {
      Claim storage thisClaim = theirClaims[i];
      if ( (0 == bytes(thisClaim.protocol).length) &&
           (0 == bytes(thisClaim.claim).length) )
      {
        return i;
      }
    }
    revert();
  }


// let user exist in case of emergency

function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        pool.lpToken.safeTransfer(address(msg.sender), amount);
        pool.totalSupply = pool.totalSupply.sub(amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }


// this rerolls all the slots of a pet besides skin/type

function rerollPet(uint256 serialId) public {
    require(isRerollAllEnabled, "Reroll is not enabled");
    require(msg.sender == ownerOf(serialId), "Only owner can reroll.");
    require(
      block.timestamp - serialIdToTimeRedeemed[serialId] <= ONE_DAY,
      "Can not reroll after one day"
    );

    string memory pet = serialIdToPet[serialId];
    string memory petSkinAsString = substring(pet, 0, 2);
    string memory petTypeAsString = substring(pet, 2, 4);
    uint256 petSkin = convertToUint(petSkinAsString);
    uint256 petType = convertToUint(petTypeAsString);

    Attributes memory attributes = generateAttributes(serialId);
    string memory rerolledPet = createPetStringRepresentation(
      petSkin,
      petType,
      attributes
    );

    if (existingPets[rerolledPet]) {
      uint256[] memory validSlots = getValidSlotsForReroll(attributes);
      uint256 randomSlotIndex = getRandomNumber(petType, petSkin, serialId) %
        validSlots.length;
      rerolledPet = _rerollPet(
        serialId,
        petSkin,
        petType,
        attributes,
        validSlots[randomSlotIndex],
        rerolledPet,
        false
      );
    }
    require(
      !existingPets[rerolledPet],
      "Could not reroll into a valid pet, please try again."
    );
    serialIdToPet[serialId] = rerolledPet;
    existingPets[rerolledPet] = true;
    existingPets[pet] = false;
    emit ZPet(msg.sender, serialId, rerolledPet, true);
  }


/// @dev ISavingStrategy.investUnderlying implementation

function investUnderlying(uint256 investAmount) external onlyOwner returns (uint256) {
        token.transferFrom(msg.sender, address(this), investAmount);
        token.approve(address(cToken), investAmount);
        uint256 cTotalBefore = cToken.totalSupply();
        // TODO should we handle mint failure?
        require(cToken.mint(investAmount) == 0, "mint failed");
        uint256 cTotalAfter = cToken.totalSupply();
        uint256 cCreatedAmount;
        require (cTotalAfter >= cTotalBefore, "Compound minted negative amount!?");
        cCreatedAmount = cTotalAfter - cTotalBefore;
        return cCreatedAmount;
    }


/// @dev ISavingStrategy.redeemUnderlying implementation

function redeemUnderlying(uint256 redeemAmount) external onlyOwner returns (uint256) {
        uint256 cTotalBefore = cToken.totalSupply();
        // TODO should we handle redeem failure?
        require(cToken.redeemUnderlying(redeemAmount) == 0, "cToken.redeemUnderlying failed");
        uint256 cTotalAfter = cToken.totalSupply();
        uint256 cBurnedAmount;
        require(cTotalAfter <= cTotalBefore, "Compound redeemed negative amount!?");
        cBurnedAmount = cTotalBefore - cTotalAfter;
        token.transfer(msg.sender, redeemAmount);
        return cBurnedAmount;
    }


/**
 * @notice Claim ERC20-compliant tokens other than locked token.
 * @param tokenToClaim Token to claim balance of.
 */

function claimToken(IERC20 tokenToClaim) external onlyBeneficiary() nonReentrant() {
        require(address(tokenToClaim) != address(token()), "smart-timelock/no-locked-token-claim");
        uint256 preAmount = token().balanceOf(address(this));

        uint256 claimableTokenAmount = tokenToClaim.balanceOf(address(this));
        require(claimableTokenAmount > 0, "smart-timelock/no-token-balance-to-claim");

        tokenToClaim.transfer(beneficiary(), claimableTokenAmount);

        uint256 postAmount = token().balanceOf(address(this));
        require(postAmount >= preAmount, "smart-timelock/locked-balance-check");

        emit ClaimToken(tokenToClaim, claimableTokenAmount);
    }


/**
 * @dev change AdminTols deployer address
 * @param _newATD new AT deployer address
 */

function changeATFactoryAddress(address _newATD) external onlyOwner {
        require(block.number < 8850000, "Time expired!");
        require(_newATD != address(0), "Address not suitable!");
        require(_newATD != ATDAddress, "AT factory address not changed!");
        ATDAddress = _newATD;
        deployerAT = IATDeployer(ATDAddress);
        emit ATFactoryAddressChanged();
    }


/**
 * @dev change Token deployer address
 * @param _newTD new T deployer address
 */

function changeTDeployerAddress(address _newTD) external onlyOwner {
        require(block.number < 8850000, "Time expired!");
        require(_newTD != address(0), "Address not suitable!");
        require(_newTD != TDAddress, "AT factory address not changed!");
        TDAddress = _newTD;
        deployerT = ITDeployer(TDAddress);
        emit TFactoryAddressChanged();
    }


/**
 * @dev change Funding Panel deployer address
 * @param _newFPD new FP deployer address
 */

function changeFPDeployerAddress(address _newFPD) external onlyOwner {
        require(block.number < 8850000, "Time expired!");
        require(_newFPD != address(0), "Address not suitable!");
        require(_newFPD != ATDAddress, "AT factory address not changed!");
        FPDAddress = _newFPD;
        deployerFP = IFPDeployer(FPDAddress);
        emit FPFactoryAddressChanged();
    }


/**
 * @dev set internal DEX address
 * @param _dexAddress internal DEX address
 */

function setInternalDEXAddress(address _dexAddress) external onlyOwner {
        require(block.number < 8850000, "Time expired!");
        require(_dexAddress != address(0), "Address not suitable!");
        require(_dexAddress != internalDEXAddress, "AT factory address not changed!");
        internalDEXAddress = _dexAddress;
        emit InternalDEXAddressChanged();
    }


/**
 * @dev getSaleData : SaleData Return
 */

function getSaleData(uint256 id) external view
        returns (
            address
            ,bool
            ,uint256
            ,uint256
            ,address
            ,uint256
            ,uint256
            ,address
            ,uint256
            ,uint256
            ,bool
            ,bool
        ) {
            SaleData memory sd = _sales[id];
            return (
                sd.seller
                ,sd.isAuction
                ,sd.nftId
                ,sd.volume
                ,sd.erc20
                ,sd.price
                ,sd.bid
                ,sd.buyer
                ,sd.start
                ,sd.end
                ,sd.isCanceled
                ,sd.isSettled
            );
    }


/// @dev Adds an auction to the list of open auctions. Also fires the
///  AuctionCreated event.
/// @param _tokenId The ID of the token to be put on auction.
/// @param _auction Auction to add.

function _addAuction(address _nft, uint256 _tokenId, Auction _auction) internal {
        // Require that all auctions have a duration of
        // at least one minute. (Keeps our math from getting hairy!)
        require(_auction.duration >= 1 minutes);

        nftToTokenIdToAuction[_nft][_tokenId] = _auction;

        AuctionCreated(
            address(_nft),
            uint256(_tokenId),
            uint256(_auction.startingPrice),
            uint256(_auction.endingPrice),
            uint256(_auction.duration)
        );
    }


/// @dev Returns current price of an NFT on auction. Broken into two
///  functions (this one, that computes the duration from the auction
///  structure, and the other that does the price computation) so we
///  can easily test that the price computation works correctly.

function _currentPrice(Auction storage _auction)
        internal
        view
        returns (uint256)
    {
        uint256 secondsPassed = 0;

        // A bit of insurance against negative values (or wraparound).
        // Probably not necessary (since Ethereum guarnatees that the
        // now variable doesn't ever go backwards).
        if (now > _auction.startedAt) {
            secondsPassed = now - _auction.startedAt;
        }

        return _computeCurrentPrice(
            _auction.startingPrice,
            _auction.endingPrice,
            _auction.duration,
            secondsPassed
        );
    }


/// @dev Computes owner's cut of a sale.
/// @param _price - Sale price of NFT.

function _computeCut(uint256 _price) internal view returns (uint256) {
        // NOTE: We don't use SafeMath (or similar) in this function because
        //  all of our entry functions carefully cap the maximum values for
        //  currency (at 128-bits), and ownerCut <= 10000 (see the require()
        //  statement in the ClockAuction constructor). The result of this
        //  function is always guaranteed to be <= _price.
        return _price * ownerCut / 10000;
    }


/// @dev Creates and begins a new auction.
/// @param _nftAddress - address of a deployed contract implementing
///  the Nonfungible Interface.
/// @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 time to move between starting
///  price and ending price (in seconds).
/// @param _seller - Seller, if not the message sender

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


/// @dev Returns auction info for an NFT on auction.
/// @param _nftAddress - Address of the NFT.
/// @param _tokenId - ID of NFT on auction.

function getAuction(address _nftAddress, uint256 _tokenId)
        public
        view
        returns
    (
        address seller,
        uint256 startingPrice,
        uint256 endingPrice,
        uint256 duration,
        uint256 startedAt
    ) {
        Auction storage auction = nftToTokenIdToAuction[_nftAddress][_tokenId];
        require(_isOnAuction(auction));
        return (
            auction.seller,
            auction.startingPrice,
            auction.endingPrice,
            auction.duration,
            auction.startedAt
        );
    }


/// @dev Creates and begins a new auction.
/// @param _nftAddress - The address of the NFT.
/// @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).

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