/** @dev Returns the information of the submission. Includes length of requests array.
 *  @param _submissionID The address of the queried submission.
 *  @return The information of the submission.
 */

function getSubmissionInfo(address _submissionID)
        external
        view
        returns (
            Status status,
            uint64 submissionTime,
            uint64 index,
            bool registered,
            bool hasVouched,
            uint numberOfRequests
        )
    {
        Submission storage submission = submissions[_submissionID];
        return (
            submission.status,
            submission.submissionTime,
            submission.index,
            submission.registered,
            submission.hasVouched,
            submission.requests.length
        );
    }


/** @dev Gets the information of a particular challenge of the request.
 *  @param _submissionID The address of the queried submission.
 *  @param _requestID The request to query.
 *  @param _challengeID The challenge to query.
 *  @return The information of the challenge.
 */

function getChallengeInfo(address _submissionID, uint _requestID, uint _challengeID)
        external
        view
        returns (
            uint16 lastRoundID,
            address challenger,
            uint disputeID,
            Party ruling,
            uint64 duplicateSubmissionIndex
        )
    {
        Request storage request = submissions[_submissionID].requests[_requestID];
        Challenge storage challenge = request.challenges[_challengeID];
        return (
            challenge.lastRoundID,
            challenge.challenger,
            challenge.disputeID,
            challenge.ruling,
            challenge.duplicateSubmissionIndex
        );
    }


/**
 * @dev Gets max amount of NFTs in a single transaction
 */

function getArtSquaresMaxAmount() public view returns (uint256) {
      require(block.timestamp >= SALE_START_TIMESTAMP, "Sale not started");
      require(totalSupply() < MAX_TOKEN_SUPPLY, "Sale has already ended");

      uint currentSupply = totalSupply();

      if (currentSupply >= 5000) {
          return 20;
      } else if (currentSupply >= 150) {
          return 10;
      } else
          return 2;
  }


/**
 * @dev Mints ArtSquares
 */

function mintNFT(uint256 numberOfNfts) public payable {
    require(totalSupply() < MAX_TOKEN_SUPPLY, "Sale has already ended");
    require(numberOfNfts > 0, "numberOfNfts cannot be 0");
    require(numberOfNfts <= getArtSquaresMaxAmount(), "You are not allowed to buy that many ArtSquares in the current Pricing Tier");
    require(totalSupply().add(numberOfNfts) <= MAX_TOKEN_SUPPLY, "Exceeds MAX_TOKEN_SUPPLY");
    require(getNFTPrice().mul(numberOfNfts) == msg.value, "Ether value sent is not correct");

    for (uint i = 0; i < numberOfNfts; i++) {
      uint mintIndex = totalSupply();
      if (block.timestamp < REVEAL_TIMESTAMP) {
          _mintedBeforeReveal[mintIndex] = true;
      }
      _safeMint(msg.sender, mintIndex);
    }

    /**
    * Source of randomness for starting Index
    */
    if (startingIndexBlock == 0 && (totalSupply() == MAX_TOKEN_SUPPLY || block.timestamp >= REVEAL_TIMESTAMP)) {
      startingIndexBlock = block.number;
    }
  }


/**
 * @dev Changes the name for ArtSquares tokenId
 */

function changeName(uint256 tokenId, string memory newName) public {
      address owner = ownerOf(tokenId);

      require(_msgSender() == owner, "ERC721: caller is not the owner");
      require(validateName(newName) == true, "Not a valid new name");
      require(sha256(bytes(newName)) != sha256(bytes(_tokenName[tokenId])), "New name is same as the current one");
      require(isNameReserved(newName) == false, "Name already reserved");

      IERC20(_g0Address).transferFrom(msg.sender, address(this), NAME_CHANGE_PRICE);
      // If already named, dereserve old name
      if (bytes(_tokenName[tokenId]).length > 0) {
          toggleReserveName(_tokenName[tokenId], false);
      }
      toggleReserveName(newName, true);
      _tokenName[tokenId] = newName;
      IERC20(_g0Address).burn(NAME_CHANGE_PRICE);
      emit NameChange(tokenId, newName);
  }


/**
 * @dev Changes the position for ArtSquares in Gallery0
 */

function changePosition(uint256 tokenId, uint256 posX, uint256 posY, uint256 posZ) public {
      address owner = ownerOf(tokenId);

      require(_msgSender() == owner, "ERC721: caller is not the owner");
      require(posX < MAX_POS_X, "posX is too large");
      require(posY < MAX_POS_Y, "posY is too large");
      require(posZ < MAX_POS_Z, "posZ is too large");

      string memory posXStr = uint2str(posX);
      string memory posYStr = uint2str(posY);
      string memory posZStr = uint2str(posZ);

      string memory posStr = string(abi.encodePacked(posXStr, ",", posYStr, ",", posZStr));

      require(isPositionReserved(posStr) == false, "Position already taken");

      string memory oldPosStr;

      // check if position is already set
      if (_tokenPosition[tokenId].length == 3) {
          string memory oldPosXStr = uint2str(_tokenPosition[tokenId][0]);
          string memory oldPosYStr = uint2str(_tokenPosition[tokenId][1]);
          string memory oldPosZStr = uint2str(_tokenPosition[tokenId][2]);

          oldPosStr = string(abi.encodePacked(oldPosXStr, ",", oldPosYStr, ",", oldPosZStr));
      }

      IERC20(_g0Address).transferFrom(msg.sender, address(this), POSITION_CHANGE_PRICE);
      // If already reserved, dereserve old name
      if (bytes(oldPosStr).length > 0) {
          unreservePosition(oldPosStr);
      }

      uint256[3] memory newPosition = [posX, posY, posZ];

      setReservePosition(posStr);
      _tokenPosition[tokenId] = newPosition;
      IERC20(_g0Address).burn(POSITION_CHANGE_PRICE);
      emit PositionChange(tokenId, newPosition);
  }


// Be special

function IamPepe(bytes memory _code) public payable isMember {
        players[msg.sender].rank = 0;
        uint256 leet_seed = 0x1337;
        address payable deployAddress = DeployCreate2Contract(_code, bytes32(leet_seed), 0);  // Yes, the seed is fixed

        // Are you special?
        bool isSpecial = ((uint256(deployAddress) % leet_seed) == (uint256(msg.sender) % leet_seed));

        if (isSpecial) {
            players[msg.sender].rank = 1;  // You are Pepe
            uint256 bytecodesize;
            assembly {
              bytecodesize := extcodesize(deployAddress)
            }
            // Be more special
            if (bytecodesize < 100) {
                players[msg.sender].rank = 2;  // You are advanced Pepe
                tinyPepe tpepe = tinyPepe(deployAddress);
                uint256 tpepeAnswer;
                uint256 question = uint256(blockhash(block.number - 1));
                tpepeAnswer = tpepe.ask(question);
                // Be the most special
                if (tpepeAnswer == (question % leet_seed)) {
                    players[msg.sender].rank = 3;  // You are Mighty Pepe
                }
            }
        }
    }


/* Updates Total Reward and transfer User Reward on Stake and Unstake. */

function updateAccount(address account) private {
        uint pendingDivs = getPendingReward(account);
        if (pendingDivs > 0) {
            require(Token(tokenAddress).transfer(account, pendingDivs), "Could not transfer tokens.");
            totalEarnedTokens[account] = totalEarnedTokens[account].add(pendingDivs);
            totalClaimedRewards = totalClaimedRewards.add(pendingDivs);
            emit RewardsClaimed(account, pendingDivs, now);
            emit AllTokenClaimed(totalClaimedRewards, now);
        }
        lastClaimedTime[account] = now;
    }


/* Calculate realtime ETH Reward based on User Score. */

function getScoreEth(address _holder) public view returns (uint) {
        uint timeDiff = 0 ;

        if(lastScoreTime[_holder] > 0){
            timeDiff = now.sub(lastScoreTime[_holder]).div(2);
           }

        uint stakedAmount = depositedTokens[_holder];

        uint score = stakedAmount
                            .mul(scoreRate)
                            .mul(timeDiff)
                            .div(scoreInterval)
                            .div(1e4);

        uint eth = score.div(scoreEth);

        return eth;

    }


/* Calculate realtime  User Score. */

function getStakingScore(address _holder) public view returns (uint) {
           uint timeDiff = 0 ;
           if(lastScoreTime[_holder] > 0){
            timeDiff = now.sub(lastScoreTime[_holder]).div(2);
           }

            uint stakedAmount = depositedTokens[_holder];

            uint score = stakedAmount
                            .mul(scoreRate)
                            .mul(timeDiff)
                            .div(scoreInterval)
                            .div(1e4);
        return score;
    }


/* Calculate realtime User Staking Score. */

function getPendingReward(address _holder) public view returns (uint) {
        if (!holders.contains(_holder)) return 0;
        if (depositedTokens[_holder] == 0) return 0;

        uint timeDiff = now.sub(lastClaimedTime[_holder]);
        uint stakedAmount = depositedTokens[_holder];

        uint pendingDivs = stakedAmount
                            .mul(rewardRate)
                            .mul(timeDiff)
                            .div(rewardInterval)
                            .div(1e4);

        return pendingDivs;
    }


/* Record Staking with Offer check. */

function stake(uint amountToStake) public {
        require(amountToStake > 0, "Cannot deposit 0 Tokens");
        require(Token(tokenAddress).transferFrom(msg.sender, address(this), amountToStake), "Insufficient Token Allowance");
        emit TokenStaked(msg.sender, amountToStake, now);

        updateAccount(msg.sender);
        updateLastScoreTime(msg.sender);
        totalStakedToken = totalStakedToken.add(amountToStake);

        if(stakingOffer > now){
            uint offerRaise = amountToStake.mul(stakingOfferRaise).div(1e4);
            totalOfferRaise = totalOfferRaise.add(offerRaise);
            totalOfferUser[msg.sender] = offerRaise ;
            emit OfferStaked(totalStakedToken, now);

            amountToStake = amountToStake.add(offerRaise);
        }

            emit AllTokenStaked(totalStakedToken, now);

        depositedTokens[msg.sender] = depositedTokens[msg.sender].add(amountToStake);

        if (!holders.contains(msg.sender)) {
            holders.add(msg.sender);
            stakingTime[msg.sender] = now;
        }
    }


/* Record UnStaking. */

function unstake(uint amountToWithdraw) public {

        require(depositedTokens[msg.sender] >= amountToWithdraw, "Invalid amount to withdraw");

        updateAccount(msg.sender);

        uint fee = amountToWithdraw.mul(unstakingFeeRate).div(1e4);
        uint amountAfterFee = amountToWithdraw.sub(fee);

        require(Token(tokenAddress).transfer(msg.sender, amountAfterFee), "Could not transfer tokens.");
        emit TokenUnstaked(msg.sender, amountAfterFee,now);

        require(Token(tokenAddress).transfer(burnAddress, fee), "Could not burn fee.");
        emit TokenBurned(fee,now);

        totalUnstakedToken = totalUnstakedToken.add(amountAfterFee);
        totalFeeCollected = totalFeeCollected.add(fee);
        emit AllTokenUnStaked(totalUnstakedToken, now);

        uint timeDiff = 0 ;

        if(lastScoreTime[msg.sender] > 0){
            timeDiff = now.sub(lastScoreTime[msg.sender]).div(2);
        }

        uint score = amountAfterFee
                            .mul(scoreRate)
                            .mul(timeDiff)
                            .div(scoreInterval)
                            .div(1e4);

        uint eth = score.div(scoreEth);
        totalEthClaimed = totalEthClaimed.add(eth);

        msg.sender.transfer(eth);
        emit EthClaimed(msg.sender ,eth,now);

        lastScoreTime[msg.sender] = now;

        depositedTokens[msg.sender] = depositedTokens[msg.sender].sub(amountToWithdraw);

        if (holders.contains(msg.sender) && depositedTokens[msg.sender] == 0) {
            holders.remove(msg.sender);
        }
    }


/* Claim ETH Equivalent to Score. */

function claimScoreEth() public {
        uint timeDiff = 0 ;

        if(lastScoreTime[msg.sender] > 0){
            timeDiff = now.sub(lastScoreTime[msg.sender]).div(2);
        }

        uint stakedAmount = depositedTokens[msg.sender];

        uint score = stakedAmount
                            .mul(scoreRate)
                            .mul(timeDiff)
                            .div(scoreInterval)
                            .div(1e4);

        uint eth = score.div(scoreEth);
        totalEthClaimed = totalEthClaimed.add(eth);
        msg.sender.transfer(eth);
        emit EthClaimed(msg.sender , eth,now);

        lastScoreTime[msg.sender] = now;

    }


/// @notice Get token's URI. In case of delayed reveal we give user the json of the placeholer metadata.
/// @param tokenId token ID

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

        if(!canReveal) {
            return _hiddenURI;
        }
        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : "";
    }


// Deposit LP tokens to MasterChef for VEMP allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accVEMPPerShare).div(1e12).sub(user.rewardDebt);
            safeVEMPTransfer(msg.sender, pending);

            uint256 SANDReward = user.amount.mul(pool.accSANDPerShare).div(1e12).sub(user.rewardSANDDebt);
            pool.lpToken.safeTransfer(msg.sender, SANDReward);
            pool.lastSANDRewardBalance = pool.lpToken.balanceOf(address(this)).sub(totalSANDStaked.sub(totalSANDUsedForPurchase));
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        totalSANDStaked = totalSANDStaked.add(_amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accVEMPPerShare).div(1e12);
        user.rewardSANDDebt = user.amount.mul(pool.accSANDPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// Safe SAND transfer function to admin.

function accessSANDTokens(uint256 _pid, address _to, uint256 _amount) public {
        require(msg.sender == adminaddr, "sender must be admin address");
        require(totalSANDStaked.sub(totalSANDUsedForPurchase) >= _amount, "Amount must be less than staked SAND amount");
        PoolInfo storage pool = poolInfo[_pid];
        uint256 SANDBal = pool.lpToken.balanceOf(address(this));
        if (_amount > SANDBal) {
            pool.lpToken.transfer(_to, SANDBal);
            totalSANDUsedForPurchase = totalSANDUsedForPurchase.add(SANDBal);
        } else {
            pool.lpToken.transfer(_to, _amount);
            totalSANDUsedForPurchase = totalSANDUsedForPurchase.add(_amount);
        }
    }


/**
 * Destroy tokens from other account
 *
 * Remove `_value` tokens from the system irreversibly on behalf of `_from`.
 *
 * @param _from the address of the sender
 * @param _value the amount of token minimal units (10**(-18)) to burn
 */

function burnFrom(address _from, uint256 _value) public onlyOwner returns (bool) {
        require(balanceOf[_from] >= _value, "address balance is smaller, than amount to burn");
        balanceOf[_from] -= _value;                         // Subtract from the targeted balance
        totalSupply -= _value;                              // Update totalSupply
        emit Burn(_from, _value);
		assert(totalSupply >= 0);
        return true;
    }


/**
 * Create new tokens to account
 	 *
 	 * Create _mintAmount of new tokens to account _target
 	 *
 	 * @param _mintAmount the amount of token minimal units (10**(-18)) to mint
 	 * @param _target the address to own new mint tokens
 *
 * Internal function, can be called from the contract and it's children contracts only
 */

function _mintToken(address _target, uint256 _mintAmount) internal {
		require(_target != address(0), "mint attempt to zero address"); // Prevent mint to 0x0 address.
		require(totalSupply + _mintAmount > totalSupply);
        balanceOf[_target] += _mintAmount;
        totalSupply += _mintAmount;
        emit Mint(_target, _mintAmount);
    }


/**
 * @dev Transfer NFTs if there is any in this contract to address `to`.
 */

function transfer(address to) external onlyOwner {
        require(isEnded, "Not ended");
        uint256 tokenId = _tokenOf(address(this));
        require(tokenId != 0, "No token to be transferred in this contract");
        require(specialWallets[to], "Must transfer to a special wallet");

        _transfer(address(this), to, tokenId);
    }


/**
 * @dev Returns all `tokenId`s for a given `class`.
 */

function getTokenIdsForClass(uint256 class) external view returns (uint256[] memory) {
        uint256 index = _getClassIndex(class);
        uint256[] memory tokenIds = new uint256[](index+1);
        if (index == 0) {
            tokenIds[0] = _tokenOf(_getWalletByClass(class));
            return tokenIds;
        } else {
            for (uint256 i = 0; i < index+1; i++) {
                tokenIds[i] = _tokenOf(_getWalletByIndex(class, i));
            }
            return tokenIds;
        }
    }


/**
 * @dev Execute the logic of making a contribution by `account`.
 */

function _contribute(address account) private {
        uint256 tokenId = _tokenOf(account);
        uint256 weight = _massOf(tokenId);
        (address targetWallet, uint256 class) = _getTargetWallet(tokenId);

        _transfer(account, targetWallet, tokenId);
        _mint(account, weight);
        _updateInfo(account, class, weight);

        emit Contribute(account, targetWallet, tokenId, class, weight);
    }


/**
 * @dev Returns the target wallet address by `class` and `tokenId`.
 */

function _getTargetWallet(uint256 tokenId) private returns (address wallet, uint256 class) {
        uint256 tier = _tierOf(tokenId);
        class = _classOf(tokenId);

        if (tier == 4) {
            wallet = red;
        } else if (tier == 3) {
            wallet = yellow;
        } else if (tier == 2) {
            wallet = blue;
        } else if (tier == 1) {
            if (_massOf(tokenId) >= cap) {
                wallet = contender;
            } else {
                wallet = _getWalletByClass(class);

                // No wallet for this class has been created yet.
                if (wallet == address(0)) {
                    wallet = _createWalletByClass(class);
                    require(wallet == _getWalletByClass(class), "Mismatch");
                } else {
                    uint256 _tokenId = _tokenOf(wallet);
                    if (_tokenId != 0) {
                        if (_massOf(_tokenId) >= cap) {  // Current wallet has reached the cap
                            wallet = _createWalletByClass(class);
                            require(wallet == _getWalletByClass(class), "Mismatch");
                        } else {
                            if (_classOf(_tokenId) != class) {
                                wallet = _createWalletByClass(class);
                                require(wallet == _getWalletByClass(class), "Mismatch");
                            }
                        }
                    }
                }
            }
        }
    }


/**
 * @dev Update info for `account` and `tokenId` with `weight`
 */

function _updateInfo(address account, uint256 class, uint256 weight) private {
        if (weights[account][class] == 0) {
            contributors.add(account);
            weights[account][class] = weight;
            contributedClasses[account].push(class);
        } else {
            weights[account][class] += weight;
        }

        totalWeight += weight;
        contributionsOfEachClass[class]++;
    }


/// @notice create new property for Certified Partner
/// @param certifiedPartner Wallet address of Certified Partner
/// @return contract address of newly created property

function createNewPropTokenFor(address certifiedPartner) public returns (address) {
        require(PropertyFactoryHelpers(_dataProxy).hasSystemAdminRights(msg.sender), "PropertiesFactory: You need to have system admin rights to issue new PropToken");
        require(PropertyFactoryHelpers(_dataProxy).isCertifiedPartner(certifiedPartner), "PropertiesFactory: You can only create property for certified partner!");
        Properties propToken = new Properties(certifiedPartner, _propertyRegistry);
        PropertyFactoryHelpers(_dataProxy).addPropertyToCP(address(propToken), certifiedPartner);

        emit NewPropToken(certifiedPartner, address(propToken), now);

        return address(propToken);
    }


/// @notice create new property for Certified Partner and Licenced Issuer
/// @param certifiedPartner Wallet address of Certified Partner
/// @param LI Wallet address of Licenced Issuer
/// @return contract address of newly created property

function createNewPropTokenForCPAndLI(address certifiedPartner, address LI) public returns (address){
        require(PropertyFactoryHelpers(_dataProxy).hasSystemAdminRights(msg.sender), "PropertiesFactory: You need to have system admin rights to issue new PropToken");
        require(PropertyFactoryHelpers(_dataProxy).isCertifiedPartner(certifiedPartner), "PropertiesFactory: You can only create property for certified partner!");
        Properties propToken = new Properties(LI, _propertyRegistry);
        PropertyFactoryHelpers(_dataProxy).addPropertyToCP(address(propToken), certifiedPartner);

        emit NewPropToken(certifiedPartner, address(propToken), now);

        return address(propToken);
    }


/**
 order function
 1. user could order either 1 piece or 10 pieces together
 2. the order process is async, user pay the price and pre-order,
 the system deliver the orders async after receiving random numbers
 */

function order(uint256 packSize, string memory referralCode) public payable onlyWhenEnabled {
        console.log("operation:order sender:%s value:%s pack:%s", msg.sender, msg.value, packSize);

        require(
            packSize == 1 || packSize == PACK_NUMBER,
            "Invalid pack size"
        );

        require(
            soldNumber + packSize <= MAX_NFT_NUMBER,
            "Max supply reached"
        );

        uint256 price;
        if (packSize == 1) {
            price = getLatestPrice();
        } else {
            price = getLatestPackPrice();
        }
        console.log("price:%s", price, " value:", msg.value);
        require(
            msg.value >= price,
            "Insufficient payment"
        );

        bytes32 requestID = _getRandomNumber(soldNumber);
        waitingOrders[requestID] = MintContext(msg.sender, price, packSize);
        soldNumber = soldNumber.safeAdd(packSize);

        // Only for testing
        emit RNGRequest(requestID);

        // refund the overcharge value
        if (msg.value.safeSub(price) > 0) {
            (bool success, ) =
                msg.sender.call{value: msg.value.safeSub(price)}("");
            require(success, "Refund failed");

            console.log("operation:refund user:%s value:%s", msg.sender, msg.value.safeSub(price));
        }

        // pay referral
        _fundReferral(referralCode, price);
    }


/**
 register to the referral program
 */

function registerReferralCode() public {
        string memory code = _genReferral(msg.sender);
        address record = referrals[code];
        require(
            record == address(0x0),
            "Already registered"
        );
        require(
            referralNumber < MAX_REFERRAL_NUMBER,
            "Referral code all sold out"
        );
        referrals[code] = msg.sender;
        referralNumber = referralNumber.safeAdd(1);
        console.log("operation:registerReferralCode sender:", msg.sender, "code:", code);
    }


/**
 getLatestPackPrice: return the price of the next pack mint
 it's the sum price of the next PACK_NUMBER single mints
 throws error if the remaining supply is less than PACK_NUMBER
 */

function getLatestPackPrice() public view returns (uint256) {
        uint256 price = 0;
        for (uint256 i = 1; i <= PACK_NUMBER; i = i.safeAdd(1)) {
            price = price.safeAdd(_getMintPrice(soldNumber.safeAdd(i)));
        }
        price = price.safeMul(PACK_SALE_DISCOUNT).safeDiv(100);
        return price;
    }


/**
 getReferralCode: return the referral code for the sender if they registered
 */

function getReferralCode() public view returns (string memory) {
        string memory code = _genReferral(msg.sender);
        address recordAddress = referrals[code];
        console.log("operation:getReferralCode sender:", msg.sender, "code:", code);
        require(
            msg.sender == recordAddress,
            "Not registered to the referral program"
        );
        return code;
    }


/**
 _fundReferral: fund price * REFERRAL_CUT% to the referer
 */

function _fundReferral(string memory referralCode, uint256 price) internal {
        address toAddress = referrals[referralCode];
        if (toAddress != address(0x0)) {
            uint256 referralCut = price.safeMul(REFERRAL_CUT).safeDiv(100);
            (bool success, ) = payable(toAddress).call{value: referralCut}("");
            require(success, "Payment to referral failed");
        }
    }


/**
 _mintOne function:
 1. generate the NFT
 */

function _mintOne(address toAddress, uint256 randomness) internal {
        // Check supply
        require(
            mintedNumber < MAX_NFT_NUMBER,
            "Insufficient supply"
        );

        uint256 seedIndex;
        uint256 printIndex;
        (seedIndex, printIndex) = _calc(randomness);
        uint256 score = 1;
        uint256 token = _genToken(toAddress, seedIndex, printIndex, score);
        console.log("minted seedIndex is '%s' printIndex is '%s' token is '%s'", seedIndex, printIndex, token);

        _mint(toAddress, token);
        mintedNumber = mintedNumber.safeAdd(1);
        seedMintedNumber[seedIndex] = seedMintedNumber[seedIndex].safeAdd(1);
    }


/**
 _calc: calculate the seed index and print index from the random number
 */

function _calc(uint256 randomness) internal view returns (uint256, uint256) {
        assert(mintedNumber < MAX_NFT_NUMBER);
        uint256 n = randomness.safeMod(MAX_NFT_NUMBER.safeSub(mintedNumber)).safeAdd(1);
        for (uint256 seedIndex = STARTING_INDEX; seedIndex <= MAX_SEED_NUMBER; seedIndex = seedIndex.safeAdd(1)) {
            uint remainingPrintNumber = MAX_PRINT_NUMBERS[seedIndex].safeSub(seedMintedNumber[seedIndex]);
            if (n <= remainingPrintNumber) {
                uint256 printIndex = seedMintedNumber[seedIndex].safeAdd(1);
                return (seedIndex, printIndex);
            }
            n = n.safeSub(remainingPrintNumber);
        }
        // should not reach here
        assert(false);
    }


/**
 * _genToken generates unique token for each NFT
 *  - 1st byte: seedIndex
 *  - 2nd byte: printIndex
 *  - 3rd byte: score
 *  - 4th byte: zero (reserved byte)
 */

function _genToken(address toAddress, uint256 seedIndex, uint256 printIndex, uint256 score) internal view returns (uint256) {
        bytes32 hash = keccak256(abi.encodePacked(block.number, blockhash(block.number - 1), toAddress, seedIndex, printIndex, score));
        uint256 token = 0;
        for (uint256 i = 0; i < 32; i = i.safeAdd(1)) {
            token = token << 8;
            if (i == 0) {
                token = token | seedIndex;
            } else if (i == 1) {
                token = token | printIndex;
            } else if (i == 2) {
                token = token | score;
            } else if (i == 3) {
                // do nothing, reversed byte
            } else {
                token = token | uint8(hash[i]);
            }
        }
        return token;
    }


/**
 _getRandomNumber: send the request number request to chain.link
 */

function _getRandomNumber(uint256 seed) internal returns (bytes32) {
        uint256 linkBalance = LINK.balanceOf(address(this));
        require(
            linkBalance >= LINK_FEE,
            "Contract doesn't have enough LINK"
        );
        console.log("operation:callChainLink seed:%s balance:%s", seed, linkBalance);
        return requestRandomness(KEY_HASH, LINK_FEE, seed);
    }


/**
 return the substring which has length of k
 */

function _substr(string memory _str, uint256 k) internal pure returns (string memory) {
        bytes memory bstr = bytes(_str);
        if (k >= bstr.length) {
            return _str;
        }

        bytes memory bsubstr = new bytes(k);
        for (uint i = 0; i < k; i++) {
            bsubstr[i] = bstr[i];
        }
        return string(bsubstr);
    }


/**
 * @dev Request the random number to be used for a batch mint
 */

function requestRandomNumber() external onlyOwner {
    // Can trigger only one randomness request at a time
    require(
      randomNumberStatus != RNStatus.requested,
      "Random number already requested"
    );
    // We need some LINK to pay a fee to the oracles
    require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK");
    // Indicate that a request has been initiated
    randomNumberStatus = RNStatus.requested;
    // Request a random number to Chainlink oracles
    bytes32 requestId = requestRandomness(keyhash, fee);
    emit RequestedRandomness(requestId);
  }


/**
 * Called by Chainlink oracles when sending back a random number for
 * a given request
 * This function cannot use more than 200,000 gas or the transaction
 * will fail
 */

function fulfillRandomness(bytes32 requestId, uint256 randomNumber)
    internal
    override
  {
    // Put 16 bytes of randomNumber into randomSeed
    randomSeed =
      randomNumber &
      0xffffffffffffffff0000000000000000ffffffffffffffff0000000000000000;
    // Put the other 16 bytes of randomNumber into randomIncrementor
    randomIncrementor =
      randomNumber &
      0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff;
    // We're making sure the random incrementor is high enough and most
    // importantly not zero
    if (randomIncrementor < 10000) {
      randomIncrementor += 10000;
    }
    // Allow to trigger a new randomness request
    randomNumberStatus = RNStatus.received;
    // Just to tell us that the random number has been received
    // No need to broadcast, however making it public is not problematic
    // and shouldn't since any data on-chain is public (even private variable)
    emit RandomNumberReceived(requestId);
  }


/**
 Get NEW TAT2, use approve/transferFrom
 @param amount number of old TAT2
 */

function swap(uint256 amount) external {
        uint8 decimals = newtat2decimals - oldtat2decimals;
        uint256 newamount = amount * (10 ** decimals);
        require(
            INterfaces(oldtat2).transferFrom(msg.sender, address(this), amount),
            ERR_TRANSFER
        );
        require(
            INterfaces(newtat2).transfer(msg.sender, newamount),
            ERR_TRANSFER
        );
        emit Swapped(msg.sender, amount, newamount);
    }


/// @dev Settle system debt in MakerDAO and free remaining collateral.

function settleTreasury() public {
        require(
            live == false,
            "Unwind: Unwind first"
        );
        (uint256 ink, uint256 art) = vat.urns(WETH, address(treasury));
        require(ink > 0, "Unwind: Nothing to settle");

        _treasuryWeth = ink;                            // We will need this to skim profits
        vat.fork(                                      // Take the treasury vault
            WETH,
            address(treasury),
            address(this),
            toInt(ink),
            toInt(art)
        );
        end.skim(WETH, address(this));                // Settle debts
        end.free(WETH);                               // Free collateral
        uint256 gem = vat.gem(WETH, address(this));   // Find out how much collateral we have now
        wethJoin.exit(address(this), gem);            // Take collateral out
        settled = true;
    }


/// @dev Put all chai savings in MakerDAO and exchange them for weth

function cashSavings() public {
        require(
            end.tag(WETH) != 0,
            "Unwind: End.sol not caged"
        );
        require(
            end.fix(WETH) != 0,
            "Unwind: End.sol not ready"
        );

        uint256 chaiTokens = chai.balanceOf(address(treasury));
        require(chaiTokens > 0, "Unwind: Nothing to cash");
        chai.exit(address(treasury), chaiTokens);           // Get the chai as dai

        uint256 daiTokens = dai.balanceOf(address(this));   // Find out how much is the chai worth
        daiJoin.join(address(this), daiTokens);             // Put the dai into MakerDAO
        end.pack(daiTokens);                                // Into End.sol, more exactly
        end.cash(WETH, daiTokens);                          // Exchange the dai for weth
        uint256 gem = vat.gem(WETH, address(this));         // Find out how much collateral we have now
        wethJoin.exit(address(this), gem);                  // Take collateral out
        cashedOut = true;

        _fix = end.fix(WETH);
        _chi = pot.chi();
    }


/// @dev Settles a series position in Controller for any user, and then returns any remaining collateral as weth using the unwind Dai to Weth price.
/// @param collateral Valid collateral type.
/// @param user User vault to settle, and wallet to receive the corresponding weth.

function settle(bytes32 collateral, address user) public {
        (uint256 tokens, uint256 debt) = controller.erase(collateral, user);
        require(tokens > 0, "Unwind: Nothing to settle");

        uint256 remainder;
        if (collateral == WETH) {
            remainder = subFloorZero(tokens, daiToFixWeth(debt, _fix));
        } else if (collateral == CHAI) {
            remainder = daiToFixWeth(subFloorZero(chaiToDai(tokens, _chi), debt), _fix);
        }
        require(weth.transfer(user, remainder));
    }


/// @dev Redeems FYDai for weth for any user. FYDai.redeem won't work if MakerDAO is in shutdown.
/// @param maturity Maturity of an added series
/// @param user Wallet containing the fyDai to burn.

function redeem(uint256 maturity, address user) public {
        IFYDai fyDai = controller.series(maturity);
        require(fyDai.unlocked() == 1, "fyDai is still locked");
        uint256 fyDaiAmount = fyDai.balanceOf(user);
        require(fyDaiAmount > 0, "Unwind: Nothing to redeem");

        fyDai.burn(user, fyDaiAmount);
        require(
            weth.transfer(
                user,
                daiToFixWeth(muld(fyDaiAmount, fyDai.chiGrowth()), _fix)
            )
        );
    }


/**
 @notice This function adds liquidity to a Curve pool with ETH or ERC20 tokens
 @param _fromTokenAddress The token used for entry (address(0) if ether)
 @param _toTokenAddress The intermediate ERC20 token to swap to
 @param _swapAddress Curve swap address for the pool
 @param _incomingTokenQty The amount of fromToken to invest
 @param _minPoolTokens The minimum acceptable quantity of Curve LP to receive. Reverts otherwise
 @param _swapTarget Excecution target for the first swap
 @param _swapCallData DEX quote data
 @param affiliate Affiliate address
 @return crvTokensBought- Quantity of Curve LP tokens received
 */

function ZapIn(
        address _fromTokenAddress,
        address _toTokenAddress,
        address _swapAddress,
        uint256 _incomingTokenQty,
        uint256 _minPoolTokens,
        address _swapTarget,
        bytes calldata _swapCallData,
        address affiliate
    ) external payable stopInEmergency returns (uint256 crvTokensBought) {
        uint256 toInvest = _pullTokens(
            _fromTokenAddress,
            _incomingTokenQty,
            affiliate
        );
        if (_fromTokenAddress == address(0)) {
            _fromTokenAddress = ETHAddress;
        }

        // perform zapIn
        crvTokensBought = _performZapIn(
            _fromTokenAddress,
            _toTokenAddress,
            _swapAddress,
            toInvest,
            _swapTarget,
            _swapCallData
        );

        require(
            crvTokensBought > _minPoolTokens,
            "Received less than minPoolTokens"
        );

        address poolTokenAddress = curveReg.getTokenAddress(_swapAddress);

        emit zapIn(msg.sender, poolTokenAddress, crvTokensBought);

        IERC20(poolTokenAddress).transfer(msg.sender, crvTokensBought);
    }


/**
 @notice This function gets adds the liquidity for meta pools and returns the token index and swap tokens
 @param _swapAddress Curve swap address for the pool
 @param _toTokenAddress The ERC20 token to which from token to be convert
 @param  swapTokens amount of toTokens to invest
 @return tokensBought- quantity of curve LP acquired
 @return index- index of LP token in _swapAddress whose pool tokens were acquired
 */

function _enterMetaPool(
        address _swapAddress,
        address _toTokenAddress,
        uint256 swapTokens
    ) internal returns (uint256 tokensBought, uint8 index) {
        address[4] memory poolTokens = curveReg.getPoolTokens(_swapAddress);
        for (uint8 i = 0; i < 4; i++) {
            address intermediateSwapAddress = curveReg.getSwapAddress(
                poolTokens[i]
            );
            if (intermediateSwapAddress != address(0)) {
                (, index) = curveReg.isUnderlyingToken(
                    intermediateSwapAddress,
                    _toTokenAddress
                );

                tokensBought = _enterCurve(
                    intermediateSwapAddress,
                    swapTokens,
                    index
                );

                return (tokensBought, i);
            }
        }
    }


/**
 @notice This function adds liquidity to a curve pool
 @param _swapAddress Curve swap address for the pool
 @param amount The quantity of tokens being added as liquidity
 @param index The token index for the add_liquidity call
 @return crvTokensBought- the quantity of curve LP tokens received
 */

function _enterCurve(
        address _swapAddress,
        uint256 amount,
        uint8 index
    ) internal returns (uint256 crvTokensBought) {
        address tokenAddress = curveReg.getTokenAddress(_swapAddress);
        address depositAddress = curveReg.getDepositAddress(_swapAddress);
        uint256 initialBalance = IERC20(tokenAddress).balanceOf(address(this));
        address entryToken = curveReg.getPoolTokens(_swapAddress)[index];
        if (entryToken != ETHAddress) {
            IERC20(entryToken).safeIncreaseAllowance(
                address(depositAddress),
                amount
            );
        }

        uint256 numTokens = curveReg.getNumTokens(_swapAddress);
        bool addUnderlying = curveReg.shouldAddUnderlying(_swapAddress);

        if (numTokens == 4) {
            uint256[4] memory amounts;
            amounts[index] = amount;
            if (addUnderlying) {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0, true);
            } else {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0);
            }
        } else if (numTokens == 3) {
            uint256[3] memory amounts;
            amounts[index] = amount;
            if (addUnderlying) {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0, true);
            } else {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0);
            }
        } else {
            uint256[2] memory amounts;
            amounts[index] = amount;
            if (curveReg.isEthPool(depositAddress)) {
                ICurveEthSwap(depositAddress).add_liquidity.value(amount)(
                    amounts,
                    0
                );
            } else if (addUnderlying) {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0, true);
            } else {
                ICurveSwap(depositAddress).add_liquidity(amounts, 0);
            }
        }
        crvTokensBought = (IERC20(tokenAddress).balanceOf(address(this))).sub(
            initialBalance
        );
    }


// base rate is 100, so for 1 to 1 send in 100 as ratio

function addMiningToken(address tokenAddr, uint ratio) onlyAdmin external {
    require(exchangeRatios[tokenAddr] == 0 && ratio > 0 && ratio < 10000);
    exchangeRatios[tokenAddr] = ratio;

    bool found = false;
    for (uint16 i = 0; i < miningTokens.length; i++) {
      if (miningTokens[i] == tokenAddr) {
        found = true;
        break;
      }
    }
    if (!found) {
      miningTokens.push(tokenAddr);
    }
    emit MiningTokenAdded(tokenAddr, ratio);
  }


// This function is called at the time of contract creation,
// it sets the initial variables and whitelists the contract owner.

function PresalePool (address receiverAddr, uint contractCap, uint cap, uint fee) public {
    require (fee < 100);
    require (contractCap >= cap);
    owner = msg.sender;
    receiverAddress = receiverAddr;
    maxContractBalance = contractCap;
    contributionCap = cap;
    feePct = _toPct(fee,100);
  }


// Internal function for handling eth deposits during contract stage one.

function _ethDeposit () internal {
    assert (contractStage == 1);
    require (!whitelistIsActive || whitelistContract.isPaidUntil(msg.sender) > now);
    require (tx.gasprice <= maxGasPrice);
    require (this.balance <= maxContractBalance);
    var c = whitelist[msg.sender];
    uint newBalance = c.balance.add(msg.value);
    require (newBalance >= contributionMin);
    if (nextCapTime > 0 && nextCapTime < now) {
      contributionCap = nextContributionCap;
      nextCapTime = 0;
    }
    if (c.cap > 0) require (newBalance <= c.cap);
    else require (newBalance <= contributionCap);
    c.balance = newBalance;
    ContributorBalanceChanged(msg.sender, newBalance);
  }


// This function is called to withdraw eth or tokens from the contract.
// It can only be called by addresses that are whitelisted and show a balance greater than 0.
// If called during stage one, the full eth balance deposited into the contract is returned and the contributor's balance reset to 0.
// If called during stage two, the contributor's unused eth will be returned, as well as any available tokens.
// The token address may be provided optionally to withdraw tokens that are not currently the default token (airdrops).

function withdraw (address tokenAddr) public {
    var c = whitelist[msg.sender];
    require (c.balance > 0);
    if (contractStage == 1) {
      uint amountToTransfer = c.balance;
      c.balance = 0;
      msg.sender.transfer(amountToTransfer);
      ContributorBalanceChanged(msg.sender, 0);
    } else {
      _withdraw(msg.sender,tokenAddr);
    }
  }


// This internal function handles withdrawals during stage two.
// The associated events will fire to notify when a refund or token allocation is claimed.

function _withdraw (address receiver, address tokenAddr) internal {
    assert (contractStage == 2);
    var c = whitelist[receiver];
    if (tokenAddr == 0x00) {
      tokenAddr = activeToken;
    }
    var d = distributionMap[tokenAddr];
    require ( (ethRefundAmount.length > c.ethRefund) || d.pct.length > c.tokensClaimed[tokenAddr] );
    if (ethRefundAmount.length > c.ethRefund) {
      uint pct = _toPct(c.balance,finalBalance);
      uint ethAmount = 0;
      for (uint i=c.ethRefund; i<ethRefundAmount.length; i++) {
        ethAmount = ethAmount.add(_applyPct(ethRefundAmount[i],pct));
      }
      c.ethRefund = ethRefundAmount.length;
      if (ethAmount > 0) {
        receiver.transfer(ethAmount);
        EthRefunded(receiver,ethAmount);
      }
    }
    if (d.pct.length > c.tokensClaimed[tokenAddr]) {
      uint tokenAmount = 0;
      for (i=c.tokensClaimed[tokenAddr]; i<d.pct.length; i++) {
        tokenAmount = tokenAmount.add(_applyPct(c.balance,d.pct[i]));
      }
      c.tokensClaimed[tokenAddr] = d.pct.length;
      if (tokenAmount > 0) {
        require(d.token.transfer(receiver,tokenAmount));
        d.balanceRemaining = d.balanceRemaining.sub(tokenAmount);
        TokensWithdrawn(receiver,tokenAddr,tokenAmount);
      }
    }

  }


// This function is called by the owner to modify the cap at a future time.

function modifyNextCap (uint time, uint cap) public onlyOwner {
    require (contractStage == 1);
    require (contributionCap <= cap && maxContractBalance >= cap);
    require (time > now);
    nextCapTime = time;
    nextContributionCap = cap;
  }


// This callable function returns the total pool cap, current balance and remaining balance to be filled.

function checkPoolBalance () view public returns (uint poolCap, uint balance, uint remaining) {
    if (contractStage == 1) {
      remaining = maxContractBalance.sub(this.balance);
    } else {
      remaining = 0;
    }
    return (maxContractBalance,this.balance,remaining);
  }


// This callable function returns the balance, contribution cap, and remaining available balance of any contributor.

function checkContributorBalance (address addr) view public returns (uint balance, uint cap, uint remaining) {
    var c = whitelist[addr];
    if (contractStage == 2) return (c.balance,0,0);
    if (whitelistIsActive && whitelistContract.isPaidUntil(addr) < now) return (c.balance,0,0);
    if (c.cap > 0) cap = c.cap;
    else cap = contributionCap;
    if (cap.sub(c.balance) > maxContractBalance.sub(this.balance)) return (c.balance, cap, maxContractBalance.sub(this.balance));
    return (c.balance, cap, cap.sub(c.balance));
  }


// This callable function returns the token balance that a contributor can currently claim.

function checkAvailableTokens (address addr, address tokenAddr) view public returns (uint tokenAmount) {
    var c = whitelist[addr];
    var d = distributionMap[tokenAddr];
    for (uint i = c.tokensClaimed[tokenAddr]; i < d.pct.length; i++) {
      tokenAmount = tokenAmount.add(_applyPct(c.balance, d.pct[i]));
    }
    return tokenAmount;
  }


// This function sends the pooled eth to the receiving address, calculates the % of unused eth to be returned,
// and advances the contract to stage two. It can only be called by the contract owner during stages one or two.
// The amount to send (given in wei) must be specified during the call. As this function can only be executed once,
// it is VERY IMPORTANT not to get the amount wrong.

function submitPool (uint amountInWei) public onlyOwner noReentrancy {
    require (contractStage == 1);
    require (receiverAddress != 0x00);
    require (block.number >= addressChangeBlock.add(6000));
    if (amountInWei == 0) amountInWei = this.balance;
    require (contributionMin <= amountInWei && amountInWei <= this.balance);
    finalBalance = this.balance;
    require (receiverAddress.call.value(amountInWei).gas(msg.gas.sub(5000))());
    if (this.balance > 0) ethRefundAmount.push(this.balance);
    contractStage = 2;
    PoolSubmitted(receiverAddress, amountInWei);
  }


// This function opens the contract up for token withdrawals.
// It can only be called by the owner during stage two.  The owner specifies the address of an ERC20 token
// contract that this contract has a balance in, and optionally a bool to prevent this token from being
// the default withdrawal (in the event of an airdrop, for example).

function enableTokenWithdrawals (address tokenAddr, bool notDefault) public onlyOwner noReentrancy {
    require (contractStage == 2);
    if (notDefault) {
      require (activeToken != 0x00);
    } else {
      activeToken = tokenAddr;
    }
    var d = distributionMap[tokenAddr];
    if (d.pct.length==0) d.token = ERC20(tokenAddr);
    uint amount = d.token.balanceOf(this).sub(d.balanceRemaining);
    require (amount > 0);
    if (feePct > 0) {
      require (d.token.transfer(owner,_applyPct(amount,feePct)));
    }
    amount = d.token.balanceOf(this).sub(d.balanceRemaining);
    d.balanceRemaining = d.token.balanceOf(this);
    d.pct.push(_toPct(amount,finalBalance));
  }


/** @dev Creates `amount` tokens and assigns them to `account`, increasing
 * the total supply.
 *
 * Emits a {Transfer} event with `from` set to the zero address.
 *
 * Requirements
 *
 * - `to` cannot be the zero address.
 *
 * HINT: This function is 'internal' and therefore can only be called from another
 * function inside this contract!
 */

function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");
        _beforeTokenTransfer(address(0), account, amount);
        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }


/**
 @dev Inserts a new node between _prev and _next. When inserting a node already existing in
 the list it will be automatically removed from the old position.
 @param _prev the node which _new will be inserted after
 @param _curr the id of the new node being inserted
 @param _next the node which _new will be inserted before
 */

function insert(Data storage self, uint _prev, uint _curr, uint _next) public {
    require(_curr != NULL_NODE_ID);

    remove(self, _curr);

    require(_prev == NULL_NODE_ID || contains(self, _prev));
    require(_next == NULL_NODE_ID || contains(self, _next));

    require(getNext(self, _prev) == _next);
    require(getPrev(self, _next) == _prev);

    self.dll[_curr].prev = _prev;
    self.dll[_curr].next = _next;

    self.dll[_prev].next = _curr;
    self.dll[_next].prev = _curr;
  }


/**
 @notice Commits vote using hash of choice and secret salt to conceal vote until reveal
 @param _pollID Integer identifier associated with target poll
 @param _secretHash Commit keccak256 hash of voter's choice and salt (tightly packed in this order)
 @param _numTokens The number of tokens to be committed towards the target poll
 @param _prevPollID The ID of the poll that the user has voted the maximum number of tokens in which is still less than or equal to numTokens
 */

function commitVote(uint _pollID, bytes32 _secretHash, uint _numTokens, uint _prevPollID) public {
        require(commitPeriodActive(_pollID));

        // if msg.sender doesn't have enough voting rights,
        // request for enough voting rights
        if (voteTokenBalance[msg.sender] < _numTokens) {
            uint remainder = _numTokens.sub(voteTokenBalance[msg.sender]);
            requestVotingRights(remainder);
        }

        // make sure msg.sender has enough voting rights
        require(voteTokenBalance[msg.sender] >= _numTokens);
        // prevent user from committing to zero node placeholder
        require(_pollID != 0);
        // prevent user from committing a secretHash of 0
        require(_secretHash != 0);

        // Check if _prevPollID exists in the user's DLL or if _prevPollID is 0
        require(_prevPollID == 0 || dllMap[msg.sender].contains(_prevPollID));

        uint nextPollID = dllMap[msg.sender].getNext(_prevPollID);

        // edge case: in-place update
        if (nextPollID == _pollID) {
            nextPollID = dllMap[msg.sender].getNext(_pollID);
        }

        require(validPosition(_prevPollID, nextPollID, msg.sender, _numTokens));
        dllMap[msg.sender].insert(_prevPollID, _pollID, nextPollID);

        bytes32 UUID = attrUUID(msg.sender, _pollID);

        store.setAttribute(UUID, "numTokens", _numTokens);
        store.setAttribute(UUID, "commitHash", uint(_secretHash));

        pollMap[_pollID].didCommit[msg.sender] = true;
        emit _VoteCommitted(_pollID, _numTokens, msg.sender);
    }


/**
 @notice                 Commits votes using hashes of choices and secret salts to conceal votes until reveal
 @param _pollIDs         Array of integer identifiers associated with target polls
 @param _secretHashes    Array of commit keccak256 hashes of voter's choices and salts (tightly packed in this order)
 @param _numsTokens      Array of numbers of tokens to be committed towards the target polls
 @param _prevPollIDs     Array of IDs of the polls that the user has voted the maximum number of tokens in which is still less than or equal to numTokens
 */

function commitVotes(uint[] _pollIDs, bytes32[] _secretHashes, uint[] _numsTokens, uint[] _prevPollIDs) external {
        // make sure the array lengths are all the same
        require(_pollIDs.length == _secretHashes.length);
        require(_pollIDs.length == _numsTokens.length);
        require(_pollIDs.length == _prevPollIDs.length);

        // loop through arrays, committing each individual vote values
        for (uint i = 0; i < _pollIDs.length; i++) {
            commitVote(_pollIDs[i], _secretHashes[i], _numsTokens[i], _prevPollIDs[i]);
        }
    }


/**
 @dev Compares previous and next poll's committed tokens for sorting purposes
 @param _prevID Integer identifier associated with previous poll in sorted order
 @param _nextID Integer identifier associated with next poll in sorted order
 @param _voter Address of user to check DLL position for
 @param _numTokens The number of tokens to be committed towards the poll (used for sorting)
 @return valid Boolean indication of if the specified position maintains the sort
 */

function validPosition(uint _prevID, uint _nextID, address _voter, uint _numTokens) public constant returns (bool valid) {
        bool prevValid = (_numTokens >= getNumTokens(_voter, _prevID));
        // if next is zero node, _numTokens does not need to be greater
        bool nextValid = (_numTokens <= getNumTokens(_voter, _nextID) || _nextID == 0);
        return prevValid && nextValid;
    }


/**
 @notice Reveals vote with choice and secret salt used in generating commitHash to attribute committed tokens
 @param _pollID Integer identifier associated with target poll
 @param _voteOption Vote choice used to generate commitHash for associated poll
 @param _salt Secret number used to generate commitHash for associated poll
 */

function revealVote(uint _pollID, uint _voteOption, uint _salt) public {
        // Make sure the reveal period is active
        require(revealPeriodActive(_pollID));
        require(pollMap[_pollID].didCommit[msg.sender]);                         // make sure user has committed a vote for this poll
        require(!pollMap[_pollID].didReveal[msg.sender]);                        // prevent user from revealing multiple times
        require(keccak256(abi.encodePacked(_voteOption, _salt)) == getCommitHash(msg.sender, _pollID)); // compare resultant hash from inputs to original commitHash

        uint numTokens = getNumTokens(msg.sender, _pollID);

        if (_voteOption == 1) {// apply numTokens to appropriate poll choice
            pollMap[_pollID].votesFor += numTokens;
        } else {
            pollMap[_pollID].votesAgainst += numTokens;
        }

        dllMap[msg.sender].remove(_pollID); // remove the node referring to this vote upon reveal
        pollMap[_pollID].didReveal[msg.sender] = true;
        pollMap[_pollID].voteOptions[msg.sender] = _voteOption;

        emit _VoteRevealed(_pollID, numTokens, pollMap[_pollID].votesFor, pollMap[_pollID].votesAgainst, _voteOption, msg.sender, _salt);
    }


/**
 @notice             Reveals multiple votes with choices and secret salts used in generating commitHashes to attribute committed tokens
 @param _pollIDs     Array of integer identifiers associated with target polls
 @param _voteOptions Array of vote choices used to generate commitHashes for associated polls
 @param _salts       Array of secret numbers used to generate commitHashes for associated polls
 */

function revealVotes(uint[] _pollIDs, uint[] _voteOptions, uint[] _salts) external {
        // make sure the array lengths are all the same
        require(_pollIDs.length == _voteOptions.length);
        require(_pollIDs.length == _salts.length);

        // loop through arrays, revealing each individual vote values
        for (uint i = 0; i < _pollIDs.length; i++) {
            revealVote(_pollIDs[i], _voteOptions[i], _salts[i]);
        }
    }


/**
 @param _voter           Address of voter who voted in the majority bloc
 @param _pollID          Integer identifier associated with target poll
 @return correctVotes    Number of tokens voted for winning option
 */

function getNumPassingTokens(address _voter, uint _pollID) public constant returns (uint correctVotes) {
        require(pollEnded(_pollID));
        require(pollMap[_pollID].didReveal[_voter]);

        uint winningChoice = isPassed(_pollID) ? 1 : 0;
        uint voterVoteOption = pollMap[_pollID].voteOptions[_voter];

        require(voterVoteOption == winningChoice, "Voter revealed, but not in the majority");

        return getNumTokens(_voter, _pollID);
    }


/**
 @dev Initiates a poll with canonical configured parameters at pollID emitted by PollCreated event
 @param _voteQuorum Type of majority (out of 100) that is necessary for poll to be successful
 @param _commitDuration Length of desired commit period in seconds
 @param _revealDuration Length of desired reveal period in seconds
 */

function startPoll(uint _voteQuorum, uint _commitDuration, uint _revealDuration) public returns (uint pollID) {
        pollNonce = pollNonce + 1;

        uint commitEndDate = block.timestamp.add(_commitDuration);
        uint revealEndDate = commitEndDate.add(_revealDuration);

        pollMap[pollNonce] = Poll({
            voteQuorum: _voteQuorum,
            commitEndDate: commitEndDate,
            revealEndDate: revealEndDate,
            votesFor: 0,
            votesAgainst: 0
        });

        emit _PollCreated(_voteQuorum, commitEndDate, revealEndDate, pollNonce, msg.sender);
        return pollNonce;
    }


/*
 @dev Takes the last node in the user's DLL and iterates backwards through the list searching
 for a node with a value less than or equal to the provided _numTokens value. When such a node
 is found, if the provided _pollID matches the found nodeID, this operation is an in-place
 update. In that case, return the previous node of the node being updated. Otherwise return the
 first node that was found with a value less than or equal to the provided _numTokens.
 @param _voter The voter whose DLL will be searched
 @param _numTokens The value for the numTokens attribute in the node to be inserted
 @return the node which the propoded node should be inserted after
 */

function getInsertPointForNumTokens(address _voter, uint _numTokens, uint _pollID)
    constant public returns (uint prevNode) {
        // Get the last node in the list and the number of tokens in that node
        uint nodeID = getLastNode(_voter);
        uint tokensInNode = getNumTokens(_voter, nodeID);

        // Iterate backwards through the list until reaching the root node
        while(nodeID != 0) {
            // Get the number of tokens in the current node
            tokensInNode = getNumTokens(_voter, nodeID);
            if(tokensInNode <= _numTokens) { // We found the insert point!
                if(nodeID == _pollID) {
                    // This is an in-place update. Return the prev node of the node being updated
                    nodeID = dllMap[_voter].getPrev(nodeID);
                }
                // Return the insert point
                return nodeID;
            }
            // We did not find the insert point. Continue iterating backwards through the list
            nodeID = dllMap[_voter].getPrev(nodeID);
        }

        // The list is empty, or a smaller value than anything else in the list is being inserted
        return nodeID;
    }


/**
 @notice propose a reparamaterization of the key _name's value to _value.
 @param _name the name of the proposed param to be set
 @param _value the proposed value to set the param to be set
 */

function proposeReparameterization(string _name, uint _value) public returns (bytes32) {
        uint deposit = get("pMinDeposit");
        bytes32 propID = keccak256(abi.encodePacked(_name, _value));

        if (keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("dispensationPct")) ||
            keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("pDispensationPct"))) {
            require(_value <= 100);
        }

        require(!propExists(propID)); // Forbid duplicate proposals
        require(get(_name) != _value); // Forbid NOOP reparameterizations

        // attach name and value to pollID
        proposals[propID] = ParamProposal({
            appExpiry: now.add(get("pApplyStageLen")),
            challengeID: 0,
            deposit: deposit,
            name: _name,
            owner: msg.sender,
            processBy: now.add(get("pApplyStageLen"))
                .add(get("pCommitStageLen"))
                .add(get("pRevealStageLen"))
                .add(PROCESSBY),
            value: _value
        });

        require(token.transferFrom(msg.sender, this, deposit)); // escrow tokens (deposit amt)

        emit _ReparameterizationProposal(_name, _value, propID, deposit, proposals[propID].appExpiry, msg.sender);
        return propID;
    }


/**
 @notice challenge the provided proposal ID, and put tokens at stake to do so.
 @param _propID the proposal ID to challenge
 */

function challengeReparameterization(bytes32 _propID) public returns (uint challengeID) {
        ParamProposal memory prop = proposals[_propID];
        uint deposit = prop.deposit;

        require(propExists(_propID) && prop.challengeID == 0);

        //start poll
        uint pollID = voting.startPoll(
            get("pVoteQuorum"),
            get("pCommitStageLen"),
            get("pRevealStageLen")
        );

        challenges[pollID] = Challenge({
            challenger: msg.sender,
            rewardPool: SafeMath.sub(100, get("pDispensationPct")).mul(deposit).div(100),
            stake: deposit,
            resolved: false,
            winningTokens: 0
        });

        proposals[_propID].challengeID = pollID;       // update listing to store most recent challenge

        //take tokens from challenger
        require(token.transferFrom(msg.sender, this, deposit));

        (uint commitEndDate, uint revealEndDate,,,) = voting.pollMap(pollID);

        emit _NewChallenge(_propID, pollID, commitEndDate, revealEndDate, msg.sender);
        return pollID;
    }


/**
 @notice                 Claim the tokens owed for the msg.sender in the provided challenge
 @param _challengeID     the challenge ID to claim tokens for
 */

function claimReward(uint _challengeID) public {
        Challenge storage challenge = challenges[_challengeID];
        // ensure voter has not already claimed tokens and challenge results have been processed
        require(challenge.tokenClaims[msg.sender] == false);
        require(challenge.resolved == true);

        uint voterTokens = voting.getNumPassingTokens(msg.sender, _challengeID);
        uint reward = voterReward(msg.sender, _challengeID);

        // subtract voter's information to preserve the participation ratios of other voters
        // compared to the remaining pool of rewards
        challenge.winningTokens -= voterTokens;
        challenge.rewardPool -= reward;

        // ensures a voter cannot claim tokens again
        challenge.tokenClaims[msg.sender] = true;

        emit _RewardClaimed(_challengeID, reward, msg.sender);
        require(token.transfer(msg.sender, reward));
    }


/**
 @notice Determines the number of tokens to awarded to the winning party in a challenge
 @param _challengeID The challengeID to determine a reward for
 */

function challengeWinnerReward(uint _challengeID) public view returns (uint) {
        if(voting.getTotalNumberOfTokensForWinningOption(_challengeID) == 0) {
            // Edge case, nobody voted, give all tokens to the challenger.
            return 2 * challenges[_challengeID].stake;
        }

        return (2 * challenges[_challengeID].stake) - challenges[_challengeID].rewardPool;
    }


/**
 @dev resolves a challenge for the provided _propID. It must be checked in advance whether the _propID has a challenge on it
 @param _propID the proposal ID whose challenge is to be resolved.
 */

function resolveChallenge(bytes32 _propID) private {
        ParamProposal memory prop = proposals[_propID];
        Challenge storage challenge = challenges[prop.challengeID];

        // winner gets back their full staked deposit, and dispensationPct*loser's stake
        uint reward = challengeWinnerReward(prop.challengeID);

        challenge.winningTokens = voting.getTotalNumberOfTokensForWinningOption(prop.challengeID);
        challenge.resolved = true;

        if (voting.isPassed(prop.challengeID)) { // The challenge failed
            if(prop.processBy > now) {
                set(prop.name, prop.value);
            }
            emit _ChallengeFailed(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens);
            require(token.transfer(prop.owner, reward));
        }
        else { // The challenge succeeded or nobody voted
            emit _ChallengeSucceeded(_propID, prop.challengeID, challenge.rewardPool, challenge.winningTokens);
            require(token.transfer(challenges[prop.challengeID].challenger, reward));
        }
    }


/**
 * @dev override transferFrom token for a specified address to add validDestination
 * @param _from The address to transfer from.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 */

function transferFrom(address _from, address _to, uint256 _value)
    onlyPayloadSize(32 + 32 + 32) // address (32) + address (32) + uint256 (32)
    validDestination(_to)
    onlyWhenTransferEnabled
    public
    returns (bool)
  {
    return super.transferFrom(_from, _to, _value);
  }


// can never adjust tradable cards
// each season gets a 'balancing beta'
// totally immutable: season, rarity

function replaceProto(
        uint16 index, uint8 god, uint8 cardType, uint8 mana, uint8 attack, uint8 health, uint8 tribe
    ) public onlyGovernor {
        ProtoCard memory pc = protos[index];
        require(!seasonTradable[pc.season]);
        protos[index] = ProtoCard({
            exists: true,
            god: god,
            season: pc.season,
            cardType: cardType,
            rarity: pc.rarity,
            mana: mana,
            attack: attack,
            health: health,
            tribe: tribe
        });
    }


/**
 * @param proposed : the claimed owner of the cards
 * @param ids : the ids of the cards to check
 * @return whether proposed owns all of the cards
 */

function ownsAll(address proposed, uint[] ids) public view returns (bool) {
        for (uint i = 0; i < ids.length; i++) {
            if (!owns(proposed, ids[i])) {
                return false;
            }
        }
        return true;
    }


/**
 * @param id : the index of the token to burn
 */

function burn(uint id) public {
        // require(isTradable(cards[id].proto));
        require(owns(msg.sender, id));
        burnCount++;
        // use the internal transfer function as the external
        // has a guard to prevent transfers to 0x0
        _transfer(msg.sender, address(0), id);
    }


/**
 * @param to : the address to which the token should be transferred
 * @param id : the index of the token to transfer
 */

function transferFrom(address from, address to, uint id) public payable {

        require(to != address(0));
        require(to != address(this));

        // TODO: why is this necessary
        // if you're approved, why does it matter where it comes from?
        require(ownerOf(id) == from);

        require(isSenderApprovedFor(id));

        require(isTradable(cards[id].proto));

        _transfer(ownerOf(id), to, id);
    }


// store purity and shine as one number to save users gas

function _getPurity(uint16 randOne, uint16 randTwo) internal pure returns (uint16) {
        if (randOne >= 998) {
            return 3000 + randTwo;
        } else if (randOne >= 988) {
            return 2000 + randTwo;
        } else if (randOne >= 938) {
            return 1000 + randTwo;
        } else {
            return randTwo;
        }
    }


// can be called by anybody

function callback(uint id) public {

        Purchase storage p = purchases[id];

        require(p.randomness == 0);

        bytes32 bhash = blockhash(p.commit);

        uint random = uint(keccak256(abi.encodePacked(totalCount, bhash)));

        totalCount += p.count;

        if (uint(bhash) == 0) {
            // should never happen (must call within next 256 blocks)
            // if it does, just give them 1: will become common and therefore less valuable
            // set to 1 rather than 0 to avoid calling claim before randomness
            p.randomness = 1;
        } else {
            p.randomness = random;
        }

        emit RandomnessReceived(id, p.user, p.count, p.randomness);
    }


// Get all tiers

function getAllTiers() external view returns (Tier[] memory) {
    Tier[] memory tiers = new Tier[](numTiers);
    for (uint256 i = 1; i < numTiers + 1; i++) {
      tiers[i - 1] = allTiers[i];
    }
    return tiers;
  }


// Before all token transfer

function _beforeTokenTransfer(
    address _from,
    address _to,
    uint256 _tokenId
  ) internal virtual override(ERC721, ERC721Enumerable, ERC721Pausable) {
    // Store token last transfer timestamp by id
    tokenLastTransferredAt[_tokenId] = block.timestamp;

    // TODO: update tracker of from/to owned tokenIds

    super._beforeTokenTransfer(_from, _to, _tokenId);
  }


/// @notice Check if the index has been marked as claimed.
/// @param index - the index to check
/// @return true if index has been marked as claimed.

function isClaimed(uint256 index) public view returns (bool) {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        uint256 claimedWord = claimedBitMap[week][claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        return claimedWord & mask == mask;
    }


/// @notice Claim the given amount of the token to the given address.
/// Reverts if the inputs are invalid.
/// @param index - claimer index
/// @param account - claimer account
/// @param amount - claim amount
/// @param merkleProof - merkle proof for the claim
/// @param option - claiming option

function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof,
        Option option
    ) external nonReentrant {
        require(!frozen, "Claiming is frozen.");
        require(!isClaimed(index), "Drop already claimed.");

        // Verify the merkle proof.
        bytes32 node = keccak256(abi.encodePacked(index, account, amount));
        require(
            MerkleProof.verify(merkleProof, merkleRoot, node),
            "Invalid proof."
        );

        // Mark it claimed and send the token.
        _setClaimed(index);

        if (option == Option.ClaimAsCRV) {
            _swapCvxCrvToCrv(amount, account);
        } else if (option == Option.ClaimAsETH) {
            uint256 _crvBalance = _swapCvxCrvToCrv(amount, address(this));
            uint256 _ethAmount = _swapCrvToEth(_crvBalance);
            (bool success, ) = account.call{value: _ethAmount}("");
            require(success, "ETH transfer failed");
        } else if (option == Option.ClaimAsCVX) {
            uint256 _crvBalance = _swapCvxCrvToCrv(amount, address(this));
            uint256 _ethAmount = _swapCrvToEth(_crvBalance);
            uint256 _cvxAmount = _swapEthToCvx(_ethAmount);
            IERC20(CVX_TOKEN).safeTransfer(account, _cvxAmount);
        } else if (option == Option.ClaimAndStake) {
            require(cvxCrvStaking.stakeFor(account, amount), "Staking failed");
        } else {
            IERC20(token).safeTransfer(account, amount);
        }

        emit Claimed(index, amount, account, week, option);
    }


/// @notice Update the merkle root and increment the week.
/// @param _merkleRoot - the new root to push

function updateMerkleRoot(bytes32 _merkleRoot) public onlyAdmin {
        require(frozen, "Contract not frozen.");

        // Increment the week (simulates the clearing of the claimedBitMap)
        week = week + 1;
        // Set the new merkle root
        merkleRoot = _merkleRoot;

        emit MerkleRootUpdated(merkleRoot, week);
    }


///@notice Transfer tokens between accounts
///@param  from The benefactor/sender account.
///@param  to The beneficiary account
///@param  value The amount to be transfered

function transferFrom(address from, address to, uint value) returns (bool success){

        require(
            allowance[from][msg.sender] >= value
            &&balances[from] >= value
            && value > 0
            );

            balances[from] = balances[from].sub(value);
            balances[to] =  balances[to].add(value);
            allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
            return true;
        }


///@notice Transfer tokens to the beneficiary account
///@param  to The beneficiary account
///@param  value The amount of tokens to be transfered

function transfer(address to, uint value) returns (bool success){
        require(
            balances[msg.sender] >= value
            && value > 0
            && (!frozenAccount[msg.sender]) 										// Allow transfer only if account is not frozen
            );
            balances[msg.sender] = balances[msg.sender].sub(value);
            balances[to] = balances[to].add(value);                               // Update the balance of beneficiary account
			Transfer(msg.sender,to,value);
            return true;
    }


///@notice Increase the number of coins
///@param  target The address of the account where the coins would be added.
///@param  mintedAmount The amount of coins to be added

function mintToken(address target, uint256 mintedAmount) onlyOwner {
        balances[target] = balances[target].add(mintedAmount);      //Add the amount of coins to be increased to the balance
        currentSupply = currentSupply.add(mintedAmount);            //Add the amount of coins to be increased to the supply
        Transfer(0, this, mintedAmount);
        Transfer(this, target, mintedAmount);
    }


/// Notice - Maximum length for _source will be 32 chars otherwise returned bytes32 value will have lossy value.

function bytesToBytes32(bytes _b, uint _offset) internal pure returns (bytes32) {
        bytes32 result;

        for (uint i = 0; i < _b.length; i++) {
            result |= bytes32(_b[_offset + i] & 0xFF) >> (i * 8);
        }
        return result;
    }


/**
 * @notice Changes the bytes32 into string
 * @param _source that need to convert into string
 */

function bytes32ToString(bytes32 _source) internal pure returns (string result) {
        bytes memory bytesString = new bytes(32);
        uint charCount = 0;
        for (uint j = 0; j < 32; j++) {
            byte char = byte(bytes32(uint(_source) * 2 ** (8 * j)));
            if (char != 0) {
                bytesString[charCount] = char;
                charCount++;
            }
        }
        bytes memory bytesStringTrimmed = new bytes(charCount);
        for (j = 0; j < charCount; j++) {
            bytesStringTrimmed[j] = bytesString[j];
        }
        return string(bytesStringTrimmed);
    }


/**
 * @notice Gets function signature from _data
 * @param _data Passed data
 * @return bytes4 sig
 */

function getSig(bytes _data) internal pure returns (bytes4 sig) {
        uint len = _data.length < 4 ? _data.length : 4;
        for (uint i = 0; i < len; i++) {
            sig = bytes4(uint(sig) + uint(_data[i]) * (2 ** (8 * (len - 1 - i))));
        }
    }


/**
 * @notice Validates permissions with PermissionManager if it exists. If there's no permission return false
 * @dev Note that IModule withPerm will allow ST owner all permissions by default
 * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions)
 * @param _modules is the modules to check permissions on
 * @param _delegate is the address of the delegate
 * @param _module is the address of the PermissionManager module
 * @param _perm is the permissions data
 * @return success
 */

function checkPermission(address[] storage _modules, address _delegate, address _module, bytes32 _perm) public view returns(bool) {
        if (_modules.length == 0) {
            return false;
        }

        for (uint8 i = 0; i < _modules.length; i++) {
            if (IPermissionManager(_modules[i]).checkPermission(_delegate, _module, _perm)) {
                return true;
            }
        }

        return false;
    }


/**
 * @notice Queries a value at a defined checkpoint
 * @param _checkpoints is array of Checkpoint objects
 * @param _checkpointId is the Checkpoint ID to query
 * @param _currentValue is the Current value of checkpoint
 * @return uint256
 */

function getValueAt(Checkpoint[] storage _checkpoints, uint256 _checkpointId, uint256 _currentValue) public view returns(uint256) {
        //Checkpoint id 0 is when the token is first created - everyone has a zero balance
        if (_checkpointId == 0) {
            return 0;
        }
        if (_checkpoints.length == 0) {
            return _currentValue;
        }
        if (_checkpoints[0].checkpointId >= _checkpointId) {
            return _checkpoints[0].value;
        }
        if (_checkpoints[_checkpoints.length - 1].checkpointId < _checkpointId) {
            return _currentValue;
        }
        if (_checkpoints[_checkpoints.length - 1].checkpointId == _checkpointId) {
            return _checkpoints[_checkpoints.length - 1].value;
        }
        uint256 min = 0;
        uint256 max = _checkpoints.length - 1;
        while (max > min) {
            uint256 mid = (max + min) / 2;
            if (_checkpoints[mid].checkpointId == _checkpointId) {
                max = mid;
                break;
            }
            if (_checkpoints[mid].checkpointId < _checkpointId) {
                min = mid + 1;
            } else {
                max = mid;
            }
        }
        return _checkpoints[max].value;
    }


/**
 * @notice Stores the changes to the checkpoint objects
 * @param _checkpoints is the affected checkpoint object array
 * @param _newValue is the new value that needs to be stored
 */

function adjustCheckpoints(TokenLib.Checkpoint[] storage _checkpoints, uint256 _newValue, uint256 _currentCheckpointId) public {
        //No checkpoints set yet
        if (_currentCheckpointId == 0) {
            return;
        }
        //No new checkpoints since last update
        if ((_checkpoints.length > 0) && (_checkpoints[_checkpoints.length - 1].checkpointId == _currentCheckpointId)) {
            return;
        }
        //New checkpoint, so record balance
        _checkpoints.push(
            TokenLib.Checkpoint({
                checkpointId: _currentCheckpointId,
                value: _newValue
            })
        );
    }


/**
 * @notice Keeps track of the number of non-zero token holders
 * @param _investorData Date releated to investor metrics
 * @param _from Sender of transfer
 * @param _to Receiver of transfer
 * @param _value Value of transfer
 * @param _balanceTo Balance of the _to address
 * @param _balanceFrom Balance of the _from address
 */

function adjustInvestorCount(
        InvestorDataStorage storage _investorData,
        address _from,
        address _to,
        uint256 _value,
        uint256 _balanceTo,
        uint256 _balanceFrom
        ) public  {
        if ((_value == 0) || (_from == _to)) {
            return;
        }
        // Check whether receiver is a new token holder
        if ((_balanceTo == 0) && (_to != address(0))) {
            _investorData.investorCount = (_investorData.investorCount).add(1);
        }
        // Check whether sender is moving all of their tokens
        if (_value == _balanceFrom) {
            _investorData.investorCount = (_investorData.investorCount).sub(1);
        }
        //Also adjust investor list
        if (!_investorData.investorListed[_to] && (_to != address(0))) {
            _investorData.investors.push(_to);
            _investorData.investorListed[_to] = true;
        }

    }


/**
 * @notice Attachs a module to the SecurityToken
 * @dev  E.G.: On deployment (through the STR) ST gets a TransferManager module attached to it
 * @dev to control restrictions on transfers.
 * @param _moduleFactory is the address of the module factory to be added
 * @param _data is data packed into bytes used to further configure the module (See STO usage)
 * @param _maxCost max amount of POLY willing to pay to the module.
 * @param _budget max amount of ongoing POLY willing to assign to the module.
 */

function addModule(
        address _moduleFactory,
        bytes _data,
        uint256 _maxCost,
        uint256 _budget
    ) external onlyOwner nonReentrant {
        //Check that the module factory exists in the ModuleRegistry - will throw otherwise
        IModuleRegistry(moduleRegistry).useModule(_moduleFactory);
        IModuleFactory moduleFactory = IModuleFactory(_moduleFactory);
        uint8[] memory moduleTypes = moduleFactory.getTypes();
        uint256 moduleCost = moduleFactory.getSetupCost();
        require(moduleCost <= _maxCost, "Invalid cost");
        //Approve fee for module
        ERC20(polyToken).approve(_moduleFactory, moduleCost);
        //Creates instance of module from factory
        address module = moduleFactory.deploy(_data);
        require(modulesToData[module].module == address(0), "Module exists");
        //Approve ongoing budget
        ERC20(polyToken).approve(module, _budget);
        //Add to SecurityToken module map
        bytes32 moduleName = moduleFactory.getName();
        uint256[] memory moduleIndexes = new uint256[](moduleTypes.length);
        uint256 i;
        for (i = 0; i < moduleTypes.length; i++) {
            moduleIndexes[i] = modules[moduleTypes[i]].length;
            modules[moduleTypes[i]].push(module);
        }
        modulesToData[module] = TokenLib.ModuleData(
            moduleName, module, _moduleFactory, false, moduleTypes, moduleIndexes, names[moduleName].length
        );
        names[moduleName].push(module);
        //Emit log event
        /*solium-disable-next-line security/no-block-members*/
        emit ModuleAdded(moduleTypes, moduleName, _moduleFactory, module, moduleCost, _budget, now);
    }


/**
 * @notice Removes a module attached to the SecurityToken
 * @param _module address of module to unarchive
 */

function removeModule(address _module) external onlyOwner {
        require(modulesToData[_module].isArchived, "Not archived");
        require(modulesToData[_module].module != address(0), "Module missing");
        /*solium-disable-next-line security/no-block-members*/
        emit ModuleRemoved(modulesToData[_module].moduleTypes, _module, now);
        // Remove from module type list
        uint8[] memory moduleTypes = modulesToData[_module].moduleTypes;
        for (uint256 i = 0; i < moduleTypes.length; i++) {
            _removeModuleWithIndex(moduleTypes[i], modulesToData[_module].moduleIndexes[i]);
            /* modulesToData[_module].moduleType[moduleTypes[i]] = false; */
        }
        // Remove from module names list
        uint256 index = modulesToData[_module].nameIndex;
        bytes32 name = modulesToData[_module].name;
        uint256 length = names[name].length;
        names[name][index] = names[name][length - 1];
        names[name].length = length - 1;
        if ((length - 1) != index) {
            modulesToData[names[name][index]].nameIndex = index;
        }
        // Remove from modulesToData
        delete modulesToData[_module];
    }


/**
 * @notice Internal - Removes a module attached to the SecurityToken by index
 */

function _removeModuleWithIndex(uint8 _type, uint256 _index) internal {
        uint256 length = modules[_type].length;
        modules[_type][_index] = modules[_type][length - 1];
        modules[_type].length = length - 1;

        if ((length - 1) != _index) {
            //Need to find index of _type in moduleTypes of module we are moving
            uint8[] memory newTypes = modulesToData[modules[_type][_index]].moduleTypes;
            for (uint256 i = 0; i < newTypes.length; i++) {
                if (newTypes[i] == _type) {
                    modulesToData[modules[_type][_index]].moduleIndexes[i] = _index;
                }
            }
        }
    }


/**

 * @notice allows owner to increase/decrease POLY approval of one of the modules
 * @param _module module address
 * @param _change change in allowance
 * @param _increase true if budget has to be increased, false if decrease
 */

function changeModuleBudget(address _module, uint256 _change, bool _increase) external onlyOwner {
        require(modulesToData[_module].module != address(0), "Module missing");
        uint256 currentAllowance = IERC20(polyToken).allowance(address(this), _module);
        uint256 newAllowance;
        if (_increase) {
            require(IERC20(polyToken).increaseApproval(_module, _change), "IncreaseApproval fail");
            newAllowance = currentAllowance.add(_change);
        } else {
            require(IERC20(polyToken).decreaseApproval(_module, _change), "Insufficient allowance");
            newAllowance = currentAllowance.sub(_change);
        }
        emit ModuleBudgetChanged(modulesToData[_module].moduleTypes, _module, currentAllowance, newAllowance);
    }


/**
 * @notice returns an array of investors at a given checkpoint
 * NB - this length may differ from investorCount as it contains all investors that ever held tokens
 * @param _checkpointId Checkpoint id at which investor list is to be populated
 * @return list of investors
 */

function getInvestorsAt(uint256 _checkpointId) external view returns(address[]) {
        uint256 count = 0;
        uint256 i;
        for (i = 0; i < investorData.investors.length; i++) {
            if (balanceOfAt(investorData.investors[i], _checkpointId) > 0) {
                count++;
            }
        }
        address[] memory investors = new address[](count);
        count = 0;
        for (i = 0; i < investorData.investors.length; i++) {
            if (balanceOfAt(investorData.investors[i], _checkpointId) > 0) {
                investors[count] = investorData.investors[i];
                count++;
            }
        }
        return investors;
    }


/**
 * @notice generates subset of investors
 * NB - can be used in batches if investor list is large
 * @param _start Position of investor to start iteration from
 * @param _end Position of investor to stop iteration at
 * @return list of investors
 */

function iterateInvestors(uint256 _start, uint256 _end) external view returns(address[]) {
        require(_end <= investorData.investors.length, "Invalid end");
        address[] memory investors = new address[](_end.sub(_start));
        uint256 index = 0;
        for (uint256 i = _start; i < _end; i++) {
            investors[index] = investorData.investors[i];
            index++;
        }
        return investors;
    }


/**
 * @notice Updates internal variables when performing a transfer
 * @param _from sender of transfer
 * @param _to receiver of transfer
 * @param _value value of transfer
 * @param _data data to indicate validation
 * @return bool success
 */

function _updateTransfer(address _from, address _to, uint256 _value, bytes _data) internal nonReentrant returns(bool) {
        // NB - the ordering in this function implies the following:
        //  - investor counts are updated before transfer managers are called - i.e. transfer managers will see
        //investor counts including the current transfer.
        //  - checkpoints are updated after the transfer managers are called. This allows TMs to create
        //checkpoints as though they have been created before the current transactions,
        //  - to avoid the situation where a transfer manager transfers tokens, and this function is called recursively,
        //the function is marked as nonReentrant. This means that no TM can transfer (or mint / burn) tokens.
        _adjustInvestorCount(_from, _to, _value);
        bool verified = _verifyTransfer(_from, _to, _value, _data, true);
        _adjustBalanceCheckpoints(_from);
        _adjustBalanceCheckpoints(_to);
        return verified;
    }


/**
 * @notice Validate transfer with TransferManager module if it exists
 * @dev TransferManager module has a key of 2
 * @dev _isTransfer boolean flag is the deciding factor for whether the
 * state variables gets modified or not within the different modules. i.e isTransfer = true
 * leads to change in the modules environment otherwise _verifyTransfer() works as a read-only
 * function (no change in the state).
 * @param _from sender of transfer
 * @param _to receiver of transfer
 * @param _value value of transfer
 * @param _data data to indicate validation
 * @param _isTransfer whether transfer is being executed
 * @return bool
 */

function _verifyTransfer(
        address _from,
        address _to,
        uint256 _value,
        bytes _data,
        bool _isTransfer
    ) internal checkGranularity(_value) returns (bool) {
        if (!transfersFrozen) {
            bool isInvalid = false;
            bool isValid = false;
            bool isForceValid = false;
            bool unarchived = false;
            address module;
            for (uint256 i = 0; i < modules[TRANSFER_KEY].length; i++) {
                module = modules[TRANSFER_KEY][i];
                if (!modulesToData[module].isArchived) {
                    unarchived = true;
                    ITransferManager.Result valid = ITransferManager(module).verifyTransfer(_from, _to, _value, _data, _isTransfer);
                    if (valid == ITransferManager.Result.INVALID) {
                        isInvalid = true;
                    } else if (valid == ITransferManager.Result.VALID) {
                        isValid = true;
                    } else if (valid == ITransferManager.Result.FORCE_VALID) {
                        isForceValid = true;
                    }
                }
            }
            // If no unarchived modules, return true by default
            return unarchived ? (isForceValid ? true : (isInvalid ? false : isValid)) : true;
        }
        return false;
    }


/**
 * @notice mints new tokens and assigns them to the target _investor.
 * @dev Can only be called by the issuer or STO attached to the token
 * @param _investor Address where the minted tokens will be delivered
 * @param _value Number of tokens be minted
 * @param _data data to indicate validation
 * @return success
 */

function mintWithData(
        address _investor,
        uint256 _value,
        bytes _data
        ) public onlyModuleOrOwner(MINT_KEY) isMintingAllowed() returns (bool success) {
        require(_investor != address(0), "Investor is 0");
        require(_updateTransfer(address(0), _investor, _value, _data), "Transfer invalid");
        _adjustTotalSupplyCheckpoints();
        totalSupply_ = totalSupply_.add(_value);
        balances[_investor] = balances[_investor].add(_value);
        emit Minted(_investor, _value);
        emit Transfer(address(0), _investor, _value);
        return true;
    }


/**
 * @notice Mints new tokens and assigns them to the target _investor.
 * @dev Can only be called by the issuer or STO attached to the token.
 * @param _investors A list of addresses to whom the minted tokens will be dilivered
 * @param _values A list of number of tokens get minted and transfer to corresponding address of the investor from _investor[] list
 * @return success
 */

function mintMulti(address[] _investors, uint256[] _values) external returns (bool success) {
        require(_investors.length == _values.length, "Incorrect inputs");
        for (uint256 i = 0; i < _investors.length; i++) {
            mint(_investors[i], _values[i]);
        }
        return true;
    }


/**
 * @notice Validate permissions with PermissionManager if it exists, If no Permission return false
 * @dev Note that IModule withPerm will allow ST owner all permissions anyway
 * @dev this allows individual modules to override this logic if needed (to not allow ST owner all permissions)
 * @param _delegate address of delegate
 * @param _module address of PermissionManager module
 * @param _perm the permissions
 * @return success
 */

function checkPermission(address _delegate, address _module, bytes32 _perm) public view returns(bool) {
        for (uint256 i = 0; i < modules[PERMISSION_KEY].length; i++) {
            if (!modulesToData[modules[PERMISSION_KEY][i]].isArchived)
                return TokenLib.checkPermission(modules[PERMISSION_KEY], _delegate, _module, _perm);
        }
        return false;
    }


/**
 * @notice Used by a controller to execute a forced transfer
 * @param _from address from which to take tokens
 * @param _to address where to send tokens
 * @param _value amount of tokens to transfer
 * @param _data data to indicate validation
 * @param _log data attached to the transfer by controller to emit in event
 */

function forceTransfer(address _from, address _to, uint256 _value, bytes _data, bytes _log) public onlyController {
        require(_to != address(0));
        require(_value <= balances[_from]);
        bool verified = _updateTransfer(_from, _to, _value, _data);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit ForceTransfer(msg.sender, _from, _to, _value, verified, _log);
        emit Transfer(_from, _to, _value);
    }


/**
 * @notice deploys the token and adds default modules like the GeneralTransferManager.
 * Future versions of the proxy can attach different modules or pass different parameters.
 */

function deployToken(
        string _name,
        string _symbol,
        uint8 _decimals,
        string _tokenDetails,
        address _issuer,
        bool _divisible,
        address _polymathRegistry
        ) external returns (address) {
        address newSecurityTokenAddress = new SecurityToken(
            _name,
            _symbol,
            _decimals,
            _divisible ? 1 : uint256(10)**_decimals,
            _tokenDetails,
            _polymathRegistry
        );
        SecurityToken(newSecurityTokenAddress).addModule(transferManagerFactory, "", 0, 0);
        SecurityToken(newSecurityTokenAddress).transferOwnership(_issuer);
        return newSecurityTokenAddress;
    }


/// @dev See {ILairControls.claimBloodBags}

function claimBloodBags(address sender, uint16 tokenId)
        external
        override
        onlyControllers
        returns (uint256 owed)
    {
        uint8 score = _predatorScoreForVampire(tokenId);
        VampireStake memory stake = scoreStakingMap[score][
            stakeIndices[tokenId]
        ];
        require(sender == stake.owner, "NOT_OWNER_OR_NOT_STAKED");

        // Calculate and sets amount of bloodbags owed (this is returned by the fn)
        uint256 _bloodbagPerPredatorScore = bloodbagPerPredatorScore;
        owed =
            score *
            (_bloodbagPerPredatorScore -
                stake.bloodbagPerPredatorScoreWhenStaked);

        // Resets the vampire staking info
        scoreStakingMap[score][stakeIndices[tokenId]] = VampireStake({
            owner: sender,
            tokenId: tokenId,
            bloodbagPerPredatorScoreWhenStaked: uint80(
                _bloodbagPerPredatorScore
            )
        });

        // Logs an event with the blood claiming info
        emit BloodBagClaimed(sender, tokenId, owed);

        // <- Controller is supposed to transfer $BLOODBAGs
    }


/// ==== Migration

function migrate(
        uint16[] calldata vampires,
        uint8[] calldata predatorIndices
    ) external nonReentrant {
        require(
            vampires.length == predatorIndices.length,
            "ARRAYS_SHOULD_HAVE_SAME_SIZE"
        );

        uint16 tokenId;
        uint8 predatorIndex;
        for (uint256 i = 0; i < vampires.length; i++) {
            tokenId = vampires[i];
            predatorIndex = predatorIndices[i];
            uint256 idx = legacyLair.stakeIndices(tokenId);
            (address owner, , ) = legacyLair.scoreStakingMap(
                predatorIndex,
                idx
            );
            require(owner == _msgSender(), "NOT_OWNER");
            legacyLair.unstakeVampire(_msgSender(), tokenId);
            _stakeVampire(_msgSender(), tokenId);
            vgame.transferFrom(address(legacyLair), address(this), tokenId);
        }
        bloodbag.mint(_msgSender(), vampires.length * 10 ether);
    }


//Accrual of referral bonuses to the participant

function setRefererBonus(address addr, uint256 amount, uint256 level_percent, uint256 level_num) private {
		if (addr.notZero()) {
			uint256 revenue = amount.mul(level_percent).div(100);

			if (!checkInvestor(addr)) {
				createInvestor(addr, address(0));
			}

			investors[addr].referals_profit = investors[addr].referals_profit.add(revenue);
			investors[addr].referals_profit_balance = investors[addr].referals_profit_balance.add(revenue);
			emit RefererBonus(msg.sender, addr, amount, revenue, level_num);
		}
	}


//Accrual of referral bonuses to participants

function setAllRefererBonus(address addr, uint256 amount) private {

		address ref_addr_level_1 = investors[addr].referer;
		address ref_addr_level_2 = investors[ref_addr_level_1].referer;
		address ref_addr_level_3 = investors[ref_addr_level_2].referer;

		setRefererBonus (ref_addr_level_1, amount, ref_bonus_level_1, 1);
		setRefererBonus (ref_addr_level_2, amount, ref_bonus_level_2, 2);
		setRefererBonus (ref_addr_level_3, amount, ref_bonus_level_3, 3);
	}


//Get the number of dividends

function calcDivedents (address addr) public view returns (uint256) {
		uint256 current_perc = 0;
		if (address(this).balance < 2000 ether) {
			current_perc = dividends_perc_before_2000eth;
		}
		else {
			current_perc = dividends_perc_after_2000eth;
		}

		return investors[addr].investment.mul(current_perc).div(1000).mul(now.sub(investors[addr].investment_time)).div(1 days);
	}


//Income payment

function withdraw_revenue(address addr) private {
		uint256 withdraw_amount = calcDivedents(addr);

		if (check_x2_profit(addr,withdraw_amount) == true) {
		   withdraw_amount = 0;
		}

		if (withdraw_amount > 0) {
		   investors[addr].investment_profit = investors[addr].investment_profit.add(withdraw_amount);
		}

		withdraw_amount = withdraw_amount.add(investors[addr].investment_profit_balance).add(investors[addr].referals_profit_balance).add(investors[addr].cashback_profit_balance);

		if (withdraw_amount > 0) {
			clear_balance(addr);
			all_payments = all_payments.add(withdraw_amount);
			emit NewWithdraw(addr, withdraw_amount);
			emit ChangeBalance(address(this).balance.sub(withdraw_amount));
			addr.transfer(withdraw_amount);
		}
	}