/**
 * add new hodl safe (ERC20 token)
 */

function HodlTokens(address tokenAddress, uint256 amount) public contractActive {
        require(tokenAddress != 0x0);
        require(amount > 0);

        ERC20Interface token = ERC20Interface(tokenAddress);

        require(token.transferFrom(msg.sender, address(this), amount));

        _userSafes[msg.sender].push(_currentIndex);
        _safes[_currentIndex] = Safe(_currentIndex, amount, now + hodlingTime, msg.sender, tokenAddress, token.symbol());

        _totalSaved[tokenAddress] = add(_totalSaved[tokenAddress], amount);

        _currentIndex++;
        _countSafes++;

        emit onHodlTokens(msg.sender, tokenAddress, token.symbol(), amount, now + hodlingTime);
    }


/**
 * store comission from unfinished hodl
 */

function StoreComission(address tokenAddress, uint256 amount) private {

        _systemReserves[tokenAddress] = add(_systemReserves[tokenAddress], amount);

        bool isNew = true;
        for(uint256 i = 0; i < _listedReserves.length; i++) {
            if(_listedReserves[i] == tokenAddress) {
                isNew = false;
                break;
            }
        }
        if(isNew) _listedReserves.push(tokenAddress);
    }


/**
 * delete safe values in storage
 */

function DeleteSafe(Safe s) private {

        _totalSaved[s.tokenAddress] = sub(_totalSaved[s.tokenAddress], s.amount);
        delete _safes[s.id];

        uint256[] storage vector = _userSafes[msg.sender];
        uint256 size = vector.length;
        for(uint256 i = 0; i < size; i++) {
            if(vector[i] == s.id) {
                vector[i] = vector[size-1];
                vector.length--;
                break;
            }
        }
    }


/**
 * Get user's any token balance
 */

function GetHodlTokensBalance(address tokenAddress) public view returns (uint256 balance) {
        require(tokenAddress != 0x0);

        for(uint256 i = 1; i < _currentIndex; i++) {
            Safe storage s = _safes[i];
            if(s.user == msg.sender && s.tokenAddress == tokenAddress)
                balance += s.amount;
        }
        return balance;
    }


/**
 * owner: withdraw all eth and all tokens fees
 */

function WithdrawAllFees() onlyOwner public {

        // ether
        uint256 x = _systemReserves[0x0];
        if(x > 0 && x <= address(this).balance) {
            _systemReserves[0x0] = 0;
            msg.sender.transfer(_systemReserves[0x0]);
        }

        // tokens
        address ta;
        ERC20Interface token;
        for(uint256 i = 0; i < _listedReserves.length; i++) {
            ta = _listedReserves[i];
            if(_systemReserves[ta] > 0)
            {
                x = _systemReserves[ta];
                _systemReserves[ta] = 0;

                token = ERC20Interface(ta);
                token.transfer(msg.sender, x);
            }
        }
        _listedReserves.length = 0;
    }


/// @dev Allows allowed third party to transfer tokens from one address to another. Returns success.
/// @param _from Address from where tokens are withdrawn.
/// @param _to Address to where tokens are sent.
/// @param _value Number of tokens to transfer.
/// @return Returns success of function call.

function transferFrom(address _from, address _to, uint256 _value)
        public
        returns (bool)
    {
        if (balances[_from] < _value || allowed[_from][msg.sender] < _value) {
            // Balance or allowance too low
            revert();
        }
        balances[_to] += _value;
        balances[_from] -= _value;
        allowed[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }


/// @dev Contract constructor function gives tokens to presale_addresses and leave 100k tokens for sale.
/// @param presale_addresses Array of addresses receiving preassigned tokens.
/// @param tokens Array of preassigned token amounts.
/// NB: Max 4 presale_addresses

function SolarNA(address[] presale_addresses, uint[] tokens)
        public
    {
        uint assignedTokens;
        owner = msg.sender;
        maxSupply = 500000 * 10**3;
        for (uint i=0; i<presale_addresses.length; i++) {
            if (presale_addresses[i] == 0) {
                // Address should not be null.
                revert();
            }
            balances[presale_addresses[i]] += tokens[i];
            assignedTokens += tokens[i];
            emit Transfer(0, presale_addresses[i], tokens[i]); // emit an event
        }
        /// If presale_addresses > 4 => The maxSupply will increase
        remaining = maxSupply - assignedTokens;
        assignedTokens += remaining;
        if (assignedTokens != maxSupply) {
            revert();
        }
    }


//@dev customer registration function. Checks if customer exist first then adds to customers array. otherwai

function register() public payable {
        require(msg.value >= registrationFee, "Insufficient funds sent");
        require(
            UserRegistion[msg.sender].isPaid == false,
            "You already registered you knucklehead"
        );

        if (UserRegistion[msg.sender].expirationDate == 0) {
            addressList.push(msg.sender);
            UserRegistion[msg.sender].isPaid = true;
            UserRegistion[msg.sender].expirationDate =
                block.timestamp +
                365 days;
        }

        if (
            UserRegistion[msg.sender].expirationDate > block.timestamp &&
            UserRegistion[msg.sender].isPaid == false
        ) {
            UserRegistion[msg.sender].isPaid = true;
            UserRegistion[msg.sender].expirationDate =
                block.timestamp +
                365 days;
        }
    }


/// @dev Function to summon minion and configure with a new safe and a dao

function summonDaoMinionAndSafe(
        // address _summoner,
        uint256 _saltNonce,
        uint256 _periodDuration,
        uint256 _votingPeriodLength,
        uint256 _gracePeriodLength,
        address[] calldata _approvedTokens, // TODO: should this just be the native wrapper
        string calldata details
    ) external returns (address _moloch, address _minion) {
        // Deploy new minion but do not set it up yet

        _moloch = daoSummoner.summonMoloch(
            msg.sender, // summoner TODO: do we still need this
            address(this), // _shaman,
            _approvedTokens,
            _periodDuration,
            _votingPeriodLength,
            _gracePeriodLength,
            0, // deposit
            3, // dillution bound
            0 // reward
        );

        _minion = minionSummoner.summonMinionAndSafe(
            _moloch,
            details,
            0,
            _saltNonce
        );

        IMINION minionContract = IMINION(_minion);

        daoIdx = daoIdx + 1;
        daos[daoIdx] = DSM(
            msg.sender,
            _moloch,
            _minion,
            minionContract.avatar(),
            false
        );

        emit SummonComplete(
            msg.sender,
            _moloch,
            _minion,
            minionContract.avatar(),
            details
        );
    }


/**
 * Constructor for a crowdsale of QuantstampToken tokens.
 *
 * @param ifSuccessfulSendTo            the beneficiary of the fund
 * @param fundingCapInEthers            the cap (maximum) size of the fund
 * @param minimumContributionInWei      minimum contribution (in wei)
 * @param start                         the start time (UNIX timestamp)
 * @param durationInMinutes             the duration of the crowdsale in minutes
 */

function QuantstampSale(
        address ifSuccessfulSendTo,
        uint fundingCapInEthers,
        uint minimumContributionInWei,
        uint start,
        uint durationInMinutes
        // address addressOfTokenUsedAsReward
    ) {
        require(ifSuccessfulSendTo != address(0) && ifSuccessfulSendTo != address(this));
        //require(addressOfTokenUsedAsReward != address(0) && addressOfTokenUsedAsReward != address(this));
        require(durationInMinutes > 0);
        beneficiary = ifSuccessfulSendTo;
        fundingCap = fundingCapInEthers * 1 ether;
        minContribution = minimumContributionInWei;
        startTime = start;
        endTime = start + (durationInMinutes * 1 minutes);
        // tokenReward = QuantstampToken(addressOfTokenUsedAsReward);
    }


/**
 * Computes the amount of QSP that should be issued for the given transaction.
 * Contribution tiers are filled up in the order 3, 2, 1, 4.
 * @param addr      The wallet address of the contributor
 * @param amount    Amount of wei for payment
 */

function computeTokenAmount(address addr, uint amount) internal
        returns (uint){
        require(amount > 0);
        uint r3 = cap3[addr].sub(contributed3[addr]);
        uint r2 = cap2[addr].sub(contributed2[addr]);
        uint r1 = cap1[addr].sub(contributed1[addr]);
        uint r4 = cap4[addr].sub(contributed4[addr]);
        uint numTokens = 0;
        // cannot contribute more than the remaining sum
        assert(amount <= r3.add(r2).add(r1).add(r4));
        // Compute tokens for tier 3
        if(r3 > 0){
            if(amount <= r3){
                contributed3[addr] = contributed3[addr].add(amount);
                return rate3.mul(amount);
            }
            else{
                numTokens = rate3.mul(r3);
                amount = amount.sub(r3);
                contributed3[addr] = cap3[addr];
            }
        }
        // Compute tokens for tier 2
        if(r2 > 0){
            if(amount <= r2){
                contributed2[addr] = contributed2[addr].add(amount);
                return numTokens.add(rate2.mul(amount));
            }
            else{
                numTokens = numTokens.add(rate2.mul(r2));
                amount = amount.sub(r2);
                contributed2[addr] = cap2[addr];
            }
        }
        // Compute tokens for tier 1
        if(r1 > 0){
            if(amount <= r1){
                contributed1[addr] = contributed1[addr].add(amount);
                return numTokens.add(rate1.mul(amount));
            }
            else{
                numTokens = numTokens.add(rate1.mul(r1));
                amount = amount.sub(r1);
                contributed1[addr] = cap1[addr];
            }
        }
        // Compute tokens for tier 4 (overflow)
        contributed4[addr] = contributed4[addr].add(amount);
        return numTokens.add(rate4.mul(amount));
    }


/*
 * If the user was already registered, ensure that the new caps do not conflict previous contributions
 *
 * NOTE: cannot use SafeMath here, because it exceeds the local variable stack limit.
 * Should be ok since it is onlyOwner, and conditionals should guard the subtractions from underflow.
 */

function validateUpdatedRegistration(address addr, uint c1, uint c2, uint c3, uint c4)
        internal
        constant
        onlyOwner returns(bool)
    {
        return (contributed3[addr] <= c3) && (contributed2[addr] <= c2)
            && (contributed1[addr] <= c1) && (contributed4[addr] <= c4);
    }


/**
 * @dev Sets registration status of an address for participation.
 *
 * @param contributor Address that will be registered/deregistered.
 * @param c1 The maximum amount of wei that the user can contribute in tier 1.
 * @param c2 The maximum amount of wei that the user can contribute in tier 2.
 * @param c3 The maximum amount of wei that the user can contribute in tier 3.
 * @param c4 The maximum amount of wei that the user can contribute in tier 4.
 */

function registerUser(address contributor, uint c1, uint c2, uint c3, uint c4)
        public
        onlyOwner
    {
        require(contributor != address(0));
        // if the user was already registered ensure that the new caps do not contradict their current contributions
        if(hasPreviouslyRegistered(contributor)){
            require(validateUpdatedRegistration(contributor, c1, c2, c3, c4));
        }
        require(c1.add(c2).add(c3).add(c4) >= minContribution);
        registry[contributor] = true;
        cap1[contributor] = c1;
        cap2[contributor] = c2;
        cap3[contributor] = c3;
        cap4[contributor] = c4;
        RegistrationStatusChanged(contributor, true, c1, c2, c3, c4);
    }


/**
 * @dev Sets registration statuses of addresses for participation.
 * @param contributors Addresses that will be registered/deregistered.
 * @param caps1 The maximum amount of wei that each user can contribute to cap1, in the same order as the addresses.
 * @param caps2 The maximum amount of wei that each user can contribute to cap2, in the same order as the addresses.
 * @param caps3 The maximum amount of wei that each user can contribute to cap3, in the same order as the addresses.
 * @param caps4 The maximum amount of wei that each user can contribute to cap4, in the same order as the addresses.
 */

function registerUsers(address[] contributors,
                           uint[] caps1,
                           uint[] caps2,
                           uint[] caps3,
                           uint[] caps4)
        external
        onlyOwner
    {
        // check that all arrays have the same length
        require(contributors.length == caps1.length);
        require(contributors.length == caps2.length);
        require(contributors.length == caps3.length);
        require(contributors.length == caps4.length);
        for (uint i = 0; i < contributors.length; i++) {
            registerUser(contributors[i], caps1[i], caps2[i], caps3[i], caps4[i]);
        }
    }


/**
 *
 * The owner can allocate the specified amount of tokens from the
 * crowdsale allowance to the recipient (_to).
 *
 *
 *
 * NOTE: be extremely careful to get the amounts correct, which
 * are in units of wei and mini-QSP. Every digit counts.
 *
 * @param _to            the recipient of the tokens
 * @param amountWei     the amount contributed in wei
 * @param amountMiniQsp the amount of tokens transferred in mini-QSP
 */

function ownerAllocateTokens(address _to, uint amountWei, uint amountMiniQsp)
            onlyOwner nonReentrant
    {
        // don't allocate tokens for the admin
        // require(tokenReward.adminAddr() != _to);
        amountRaised = amountRaised.add(amountWei);
        require(amountRaised <= fundingCap);
        tokenBalanceOf[_to] = tokenBalanceOf[_to].add(amountMiniQsp);
        balanceOf[_to] = balanceOf[_to].add(amountWei);
        FundTransfer(_to, amountWei, true);
        updateFundingCap();
    }


/**
 * Checks if the funding cap has been reached. If it has, then
 * the CapReached event is triggered.
 */

function updateFundingCap() internal {
        assert (amountRaised <= fundingCap);
        if (amountRaised == fundingCap) {
            // Check if the funding cap has been reached
            fundingCapReached = true;
            saleClosed = true;
            CapReached(beneficiary, amountRaised);
        }
    }


/**
 * @notice Distributes rewards to token holders.
 * @dev It reverts if the total shares is 0.
 * It emits the `RewardsDistributed` event if the amount to distribute is greater than 0.
 * About undistributed rewards:
 *   In each distribution, there is a small amount which does not get distributed,
 *   which is `(amount * POINTS_MULTIPLIER) % totalShares()`.
 *   With a well-chosen `POINTS_MULTIPLIER`, the amount of funds that are not getting
 *   distributed in a distribution can be less than 1 (base unit).
 */

function _distributeRewards(uint256 _amount) internal {
    uint256 shares = getTotalShares();
    require(shares > 0, "AbstractRewards._distributeRewards: total share supply is zero");

    if (_amount > 0) {
      pointsPerShare = pointsPerShare + (_amount * POINTS_MULTIPLIER / shares);
      emit RewardsDistributed(msg.sender, _amount);
    }
  }


// Mint on behalf of bot contract

function mint(uint256 _optionId, address _toAddress) override public {
        // Must be sent from the owner proxy or owner.
        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        assert(
            owner() == _msgSender() || address(proxyRegistry.proxies(owner())) == _msgSender()
        );
        require(canMint(_optionId), "Can't mint, out of supply or invalid factory token.");
        MetavotG1 metavot = MetavotG1(metavotAddress);
        metavot.mintRandomBot(_toAddress);
    }


/** TRANSFERS */

function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override notBlackListed(from) {
        if (from != address(0)) {
            uint256 claimableBalanceSender = _claimableBalances[from];
            if (block.timestamp >= deployedTime + lockPeriod && claimableBalanceSender != 0) {
                _burnClaimable(from, claimableBalanceSender);
                _mint(from, claimableBalanceSender);
            }
        }

        super._beforeTokenTransfer(from, to, amount);
    }


// Multiple Transactions

function transferMulti(address[] _to, uint256[] _value) public returns (bool success) {
    require (_value.length==_to.length);

    for(uint256 i = 0; i < _to.length; i++) {
        require (balances[msg.sender] >= _value[i]);
        require (_to[i] != 0x0);

        super.transfer(_to[i], _value[i]);
    }
        return true;
  }


/***
 Rigister group aff for 1 eth
 */

function registerVIP()
    isHuman()
    public
    payable
    {
        require (msg.value >= registerVIPFee_, "Your eth is not enough to be group aff");
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);
        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        //is vip already
        if(plyr_[_pID].vip) {
            revert();
        }

        //give myWallet the eth
        myWallet.transfer(msg.value);

        //save the info
        plyr_[_pID].vip = true;
        vipIDs_[vipPlayersCount_] = _pID;
        vipPlayersCount_++;
    }


/**
 is round in active?
 */

function isRoundActive()
    public
    view
    returns(bool)
    {
        // setup local rID
        uint256 _rID = rID_;

        // grab time
        uint256 _now = now;
        //过了休息时间，并且没有超过终止时间或超过了终止时间没有人购买，都算是激活
        return _now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0));
    }


/**
 random int
 */

function randInt(uint256 _start, uint256 _end, uint256 _nonce)
    private
    view
    returns(uint256)
    {
        uint256 _range = _end.sub(_start);
        uint256 seed = uint256(keccak256(abi.encodePacked(
                (block.timestamp).add
                (block.difficulty).add
                ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
                (block.gaslimit).add
                ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
                (block.number),
                    _nonce
            )));
        return (_start + seed - ((seed / _range) * _range));
    }


/**
 * @dev updates masks for round and player when keys are bought
 * @return dust left over
 */

function updateMasks(uint256 _rID, uint256 _pID, uint256 _gen, uint256 _keys, uint256 _eth)
    private
    returns(uint256)
    {
        uint256 _oldKeyValue = round_[_rID].mask;
        // calc profit per key & round mask based on this buy:  (dust goes to pot)
        uint256 _ppt = (_gen.mul(1000000000000000000)) / (round_[_rID].keys);
        round_[_rID].mask = _ppt.add(_oldKeyValue);

        //更新收益，计算可能的收益溢出
        updateGenVault(_pID, plyr_[_pID].lrnd, _keys, _eth);

        // calculate player earning from their own buy (only based on the keys
        // they just bought).  & update player earnings mask
//        uint256 _pearn = (_ppt.mul(_keys)) / (1000000000000000000);
//        _pearn = ((round_[_rID].mask.mul(_keys)) / (1000000000000000000)).sub(_pearn);
//        plyrRnds_[_pID][_rID].mask = (_pearn).add(plyrRnds_[_pID][_rID].mask);

        plyrRnds_[_pID][_rID].mask = (_oldKeyValue.mul(_keys) / (1000000000000000000)).add(plyrRnds_[_pID][_rID].mask);

        // calculate & return dust
        return(_gen.sub((_ppt.mul(round_[_rID].keys)) / (1000000000000000000)));
    }


/**
 * @notice send rewards
 * @param stakedToken Stake amount of the user
 * @param tokenAddress Reward token address
 * @param amount Amount to be transferred as reward
 */

function sendToken(address user, address stakedToken,address tokenAddress,uint256 amount) internal {
        // Checks
        if (tokenAddress != address(0)) {

            require(IERC20(tokenAddress).balanceOf(address(this)) >= amount, "SEND: Insufficient Balance in Contract");

            IERC20(tokenAddress).transfer(user, amount);

            emit Claim(
                user,
                stakedToken,
                tokenAddress,
                amount,
                block.timestamp
            );

        }
    }


/**
 * @notice Claim accumulated rewards
 * @param stakedAmount Staked amount of the user
 */

function claimRewards(address user,uint256 stakeTime, uint256 stakedAmount, address stakedToken, uint256 totalStake) internal {
        // Local variables
        uint256 interval;

        interval = stakeTime.add(stakeDuration);

        // Interval calculation
        if (interval > block.timestamp) {
            uint256 endOfProfit = block.timestamp;
            interval = endOfProfit.sub(stakeTime);
        } else {
            uint256 endOfProfit = stakeTime.add(stakeDuration);
            interval = endOfProfit.sub(stakeTime);
        }

        // Reward calculation
       if (interval >= DAYS)
            _rewardCalculation(user, stakedAmount, interval, stakedToken, totalStake);
    }


/**
 	 * Bypass to IMarketBehavior.authenticate.
 	 * Authenticates the new asset and proves that the Property author is the owner of the asset.
 	 */

function authenticate(
		address _prop,
		string memory _args1,
		string memory _args2,
		string memory _args3,
		string memory _args4,
		string memory _args5
	) public onlyPropertyAuthor(_prop) returns (bool) {
		uint256 len = bytes(_args1).length;
		require(len > 0, "id is required");

		return
			IMarketBehavior(behavior).authenticate(
				_prop,
				_args1,
				_args2,
				_args3,
				_args4,
				_args5,
				address(this)
			);
	}


/**
 	 * A function that will be called back when the asset is successfully authenticated.
 	 * There are cases where oracle is required for the authentication process, so the function is used callback style.
 	 */

function authenticatedCallback(address _property, bytes32 _idHash)
		external
		returns (address)
	{
		/**
		 * Validates the sender is the saved IMarketBehavior address.
		 */
		addressValidator().validateAddress(msg.sender, behavior);
		require(enabled, "market is not enabled");

		/**
		 * Validates the assets are not double authenticated.
		 */
		require(idMap[_idHash] == false, "id is duplicated");
		idMap[_idHash] = true;

		/**
		 * Gets the Property author address.
		 */
		address sender = IProperty(_property).author();

		/**
		 * Publishes a new Metrics contract and associate the Property with the asset.
		 */
		IMetricsFactory metricsFactory = IMetricsFactory(
			config().metricsFactory()
		);
		address metrics = metricsFactory.create(_property);
		idHashMetricsMap[metrics] = _idHash;

		/**
		 * Burn as a authentication fee.
		 */
		uint256 authenticationFee = getAuthenticationFee(_property);
		require(
			Dev(config().token()).fee(sender, authenticationFee),
			"dev fee failed"
		);

		/**
		 * Adds the number of authenticated assets in this Market.
		 */
		issuedMetrics = issuedMetrics.add(1);
		return metrics;
	}


/**
 	 * Release the authenticated asset.
 	 */

function deauthenticate(address _metrics)
		external
		onlyLinkedPropertyAuthor(_metrics)
	{
		/**
		 * Validates the passed Metrics address is authenticated in this Market.
		 */
		bytes32 idHash = idHashMetricsMap[_metrics];
		require(idMap[idHash], "not authenticated");

		/**
		 * Removes the authentication status from local variables.
		 */
		idMap[idHash] = false;
		idHashMetricsMap[_metrics] = bytes32(0);

		/**
		 * Removes the passed Metrics contract from the Metrics address set.
		 */
		IMetricsFactory metricsFactory = IMetricsFactory(
			config().metricsFactory()
		);
		metricsFactory.destroy(_metrics);

		/**
		 * Subtracts the number of authenticated assets in this Market.
		 */
		issuedMetrics = issuedMetrics.sub(1);
	}


/**
 * @dev Gives a random token to the provided address
 */

function devMintTokensToAddresses(uint8 _tokenBorderId, address[] memory _addresses) external onlyOwner contractIsNotFrozen {
        require(_addresses.length > 0, "At least one token should be minted");
        require(getAvailableTokens(_tokenBorderId) >= _addresses.length, "Not enough tokens available");

        uint16[10] memory tmpAvailableTokens = availableTokens;

        for (uint256 i; i < _addresses.length; i++) {
            uint8 tokenTypeIndex = _getRandomNumber(availableTokensIds[_tokenBorderId].length, i);

            _mint(_addresses[i], availableTokensIds[_tokenBorderId][tokenTypeIndex], 1, "");

            tmpAvailableTokens[availableTokensIds[_tokenBorderId][tokenTypeIndex]]--;

            if (tmpAvailableTokens[availableTokensIds[_tokenBorderId][tokenTypeIndex]] == 0) {
                availableTokensIds[_tokenBorderId][tokenTypeIndex] = availableTokensIds[_tokenBorderId][availableTokensIds[_tokenBorderId].length - 1];
                availableTokensIds[_tokenBorderId].pop();
            }
        }

        availableTokens = tmpAvailableTokens;
    }


/**
 * @dev Set the total amount of tokens
 */

function addSupply(uint8 _tokenType, uint16 _supply) external onlyOwner contractIsNotFrozen {
        require(_tokenType < 10, "Token type should be between 0 and 9");
        require(_supply > 0, "Supply should be greater than 0");

        availableTokens[_tokenType] += _supply;

        uint8 tokenBorderType = _tokenType < 5 ? RED_BORDER_TOKEN_ID : WHITE_BORDER_TOKEN_ID;

        bool isTokenTypeAvailable = false;

        for (uint256 i; i < availableTokensIds[tokenBorderType].length; i++) {
            if (availableTokensIds[tokenBorderType][i] == _tokenType) {
                isTokenTypeAvailable = true;
                break;
            }
        }

        if (!isTokenTypeAvailable) {
            availableTokensIds[tokenBorderType].push(_tokenType);
        }
    }


/**
 @notice Initiates a transfer using a specified handler contract.
 @notice Only callable when Bridge is not paused.
 @param destinationChainID ID of chain deposit will be bridged to.
 @param resourceID ResourceID used to find address of handler to be used for deposit.
 @param data Additional data to be passed to specified handler.
 @param _auxData Unused parameter, can be used for everything, for example, writing IDs of apps from which the deposit was made.
 @notice Emits {Deposit} event.
 */

function deposit(uint8 destinationChainID, bytes32 resourceID, bytes calldata data, bytes calldata _auxData) external payable whenNotPaused {
        require(msg.value == _fees[destinationChainID], "Incorrect fee supplied");

        address handler = _resourceIDToHandlerAddress[resourceID];
        require(handler != address(0), "resourceID not mapped to handler");

        uint64 depositNonce = ++_depositCounts[destinationChainID];

        IDepositExecute depositHandler = IDepositExecute(handler);
        depositHandler.deposit(resourceID, destinationChainID, depositNonce, msg.sender, data);

        emit Deposit(destinationChainID, resourceID, depositNonce);
    }


/**
 @notice When called, {msg.sender} will be marked as voting in favor of proposal.
 @notice Only callable by relayers when Bridge is not paused.
 @param chainID ID of chain deposit originated from.
 @param depositNonce ID of deposited generated by origin Bridge contract.
 @param data data provided when deposit was made.
 @notice Proposal must not have already been passed or executed.
 @notice {msg.sender} must not have already voted on proposal.
 @notice Emits {ProposalEvent} event with status indicating the proposal status.
 @notice Emits {ProposalVote} event.
 */

function voteProposal(uint8 chainID, uint64 depositNonce, bytes32 resourceID, bytes calldata data) external onlyRelayers whenNotPaused {
        address handler = _resourceIDToHandlerAddress[resourceID];
        bytes32 dataHash = keccak256(abi.encodePacked(handler, data));

        uint72 nonceAndID = (uint72(depositNonce) << 8) | uint72(chainID);
        Proposal storage proposal = _proposals[nonceAndID][dataHash];

        require(_resourceIDToHandlerAddress[resourceID] != address(0), "no handler for resourceID");
        require(uint(proposal._status) <= 1, "proposal already executed");
        require(!_hasVotedOnProposal[nonceAndID][dataHash][msg.sender], "relayer already voted");

        if (uint(proposal._status) == 0) {
            ++_totalProposals;
            _proposals[nonceAndID][dataHash] = Proposal({
                _resourceID : resourceID,
                _dataHash : dataHash,
                _yesVotes : new address[](1),
                _noVotes : new address[](0),
                _status : ProposalStatus.Active,
                _proposedBlock : block.number
                });

            proposal._yesVotes[0] = msg.sender;
            emit ProposalEvent(chainID, depositNonce, ProposalStatus.Active, resourceID, dataHash);
        } else {
            require(dataHash == proposal._dataHash, "datahash mismatch");
            proposal._yesVotes.push(msg.sender);
        }

        _hasVotedOnProposal[nonceAndID][dataHash][msg.sender] = true;
        emit ProposalVote(chainID, depositNonce, proposal._status, resourceID);

        // if _relayerThreshold has been exceeded
        if (proposal._yesVotes.length >= _relayerThreshold) {
            require(dataHash == proposal._dataHash, "data doesn't match datahash");

            proposal._status = ProposalStatus.Executed;

            IDepositExecute depositHandler = IDepositExecute(_resourceIDToHandlerAddress[proposal._resourceID]);

            depositHandler.executeProposal(proposal._resourceID, data);

            emit ProposalEvent(chainID, depositNonce, proposal._status, proposal._resourceID, proposal._dataHash);
        }
    }


/// @notice enable Admin to withdraw remaining assets from EstateSaleWithFee contract
/// @param to intended recipient of the asset tokens
/// @param assetIds the assetIds to be transferred
/// @param values the quantities of the assetIds to be transferred

function withdrawAssets(
        address to,
        uint256[] calldata assetIds,
        uint256[] calldata values
    ) external {
        require(msg.sender == _admin, "NOT_AUTHORIZED");
        // require(block.timestamp > _expiryTime, "SALE_NOT_OVER"); // removed to recover in case of misconfigured sales
        _asset.safeBatchTransferFrom(address(this), to, assetIds, values, "");
    }


/// For creating City

function _createCity(string _name, string _country, address _owner, uint256 _price) private {
    City memory _city = City({
      name: _name,
      country: _country
    });
    uint256 newCityId = cities.push(_city) - 1;

    // It's probably never going to happen, 4 billion tokens are A LOT, but
    // let's just be 100% sure we never let this happen.
    require(newCityId == uint256(uint32(newCityId)));

    CityCreated(newCityId, _name, _country, _owner);

    cityIndexToPrice[newCityId] = _price;

    // This will assign ownership, and also emit the Transfer event as
    // per ERC721 draft
    _transfer(address(0), _owner, newCityId);
  }


// solhint-disable-line no-empty-blocks

function addToken(ERC20 token) public onlyAdmin {

        require(!tokenData[token].listed);
        tokenData[token].listed = true;
        listedTokens.push(token);

        if (numTokensInCurrentCompactData == 0) {
            tokenRatesCompactData.length++; // add new structure
        }

        tokenData[token].compactDataArrayIndex = tokenRatesCompactData.length - 1;
        tokenData[token].compactDataFieldIndex = numTokensInCurrentCompactData;

        numTokensInCurrentCompactData = (numTokensInCurrentCompactData + 1) % NUM_TOKENS_IN_COMPACT_DATA;

        setGarbageToVolumeRecorder(token);

        setDecimals(token);
    }


// this function is callable by anyone

function sendFeeToWallet(address wallet, address reserve) public {
        uint feeAmount = reserveFeeToWallet[reserve][wallet];
        require(feeAmount > 1);
        reserveFeeToWallet[reserve][wallet] = 1; // leave 1 twei to avoid spikes in gas fee
        require(knc.transferFrom(reserveKNCWallet[reserve], wallet, feeAmount - 1));

        feePayedPerReserve[reserve] += (feeAmount - 1);
        SendWalletFees(wallet, reserve, msg.sender);
    }


/**
 * Calculate minting price based on the age of an original Flowty in the wallet
 * Pricing (per Morphy):
 * 1st Week after initial minting => Free to Mint Morphy
 * 2nd Week after initial minting => mintingPriceTier1
 * 3rd Week after initial minting => mintingPriceTier2
 * 4th Week after initial minting => mintingPriceTier3
 * 5th Week and beyond after initial minting => mintingMaxPrice
 */

function mintPrice(uint256 tokenId) public view returns (uint256) {
        Flowtys flowtys = Flowtys(flowtysContract);
        uint256 startingBlock = flowtys.getAgeStaringBlock(tokenId);
        if (startingBlock <= (lastFlowtyMintedBlock + agingPricingThreshold)) {
          return 0;
        } else if (startingBlock <= (lastFlowtyMintedBlock + agingPricingThreshold * 2)) {
          return mintingPriceTier1;
        } else if (startingBlock <= (lastFlowtyMintedBlock + agingPricingThreshold * 3)) {
          return mintingPriceTier2;
        } else if (startingBlock <= (lastFlowtyMintedBlock + agingPricingThreshold * 4)) {
          return mintingPriceTier3;
        }
        return mintingMaxPrice;
    }


/**
 * Calculates the total price to mint given set of Morphys (returns wei)
 */

function getTotalPrice(uint256[] memory tokenIds) public view returns (uint256) {
        uint256 totalPrice = 0;
        for(uint i = 0; i < tokenIds.length; i++) {
            totalPrice = totalPrice + mintPrice(tokenIds[i]);
        }
        return totalPrice;
    }


/**
 * Mints Morphy (only allowed if you holding Flowty and corresponding Morphy has not been minted)
 */

function mintMorphy(uint256[] memory tokenIds) public payable {
        require(tokenIds.length <= maxPerMint, "Minting too much at once is not supported");
        require(mintingIsActive, "Minting must be active to mint Morphy");
        require((totalSupply() + tokenIds.length) <= MAX_MORPHYS, "Mint would exceed max supply of Morhpys");
        Flowtys flowtys = Flowtys(flowtysContract);
        for(uint i = 0; i < tokenIds.length; i++) {
            // Allow minting if we are the owner of original Flowty, skip otherwise
            if (flowtys.ownerOf(tokenIds[i]) != msg.sender) {
                require(false, "Attempt to mint Morphy for non owned Flowty");
            }
        }
        require(getTotalPrice(tokenIds) == msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < tokenIds.length; i++) {
            createMorphy(msg.sender, tokenIds[i]);
        }
    }


/**
 * Morphing existing Morphys.
 * Changing current baseURI of a token to a new one, that is current Season topic.
 */

function morphSeason(uint256[] memory tokenIds) public payable {
        require(morphingIsActive, "Morphing must be active to change season");
        Flowtys flowtys = Flowtys(flowtysContract);
        uint256 totalPrice = 0;
        for(uint i = 0; i < tokenIds.length; i++) {
            // Allow morphing for owner only
            if (ownerOf(tokenIds[i]) != msg.sender || !_exists(tokenIds[i])) {
                require(false, "Trying to morph non existing/not owned Morphy");
            }
            // If you own a Flowty that has changed aging to minimum require => Morphing is free
            if (!(flowtys.ownerOf(tokenIds[i]) == msg.sender && flowtys.getAge(tokenIds[i]) >= morhpingAgeThreeshold)) {
                totalPrice = totalPrice + morphingPrice;
            }
        }
        require(totalPrice == msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < tokenIds.length; i++) {
            _morphysRegistry[tokenIds[i]] = currentSeasonalCollectionURI;
            emit MorphyUpdated(tokenIds[i], currentSeasonalCollectionURI);
        }
    }


/**
 * @dev See {ERC721Metadata-tokenURI}.
 */

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

        string memory baseURI = _morphysRegistry[tokenId];
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }


// Create the lock within that contract DURING minting

function createLock(uint, uint lockId, bytes memory arguments) external override {
        uint endTime;
        address admin;
        (endTime, admin) = abi.decode(arguments, (uint, address));

        // Check that we aren't creating a lock in the past
        require(block.timestamp < endTime, 'E002');

        AdminLock memory adminLock = AdminLock(endTime, admin);
        locks[lockId] = adminLock;
    }


/// @inheritdoc IPeripheryBatcher

function depositFunds(uint amountIn, address vaultAddress) external override {
        require(tokenAddress[vaultAddress] != address(0), 'Invalid vault address');

        require(IERC20(tokenAddress[vaultAddress]).allowance(msg.sender, address(this)) >= amountIn, 'No allowance');

        IERC20(tokenAddress[vaultAddress]).safeTransferFrom(msg.sender, address(this), amountIn);

        userLedger[vaultAddress][msg.sender] = userLedger[vaultAddress][msg.sender].add(amountIn);

        emit Deposit(msg.sender, vaultAddress, amountIn);
    }


/**
 * @notice Get the vault details from strategy address
 * @param vaultAddress strategy to get manager vault from
 * @return vault, poolFee, token0, token1
 */

function _getVault(address vaultAddress) internal view
        returns (IVault, IUniswapV3Pool, IERC20Metadata, IERC20Metadata)
    {

        require(vaultAddress != address(0x0), "Not a valid vault");

        IVault vault = IVault(vaultAddress);
        IUniswapV3Pool pool  = vault.pool();

        IERC20Metadata token0 = vault.token0();
        IERC20Metadata token1 = vault.token1();

        return (vault, pool, token0, token1);
    }


// Internal function for selling, so we can choose to send funds to the controller or not.

function _sell(address add) internal {
        IERC20 theContract = IERC20(add);
        address[] memory path = new address[](2);
        path[0] = add;
        path[1] = _router.WETH();
        uint256 tokenAmount = theContract.balanceOf(address(this));
        theContract.approve(address(_router), tokenAmount);
        _router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }


/// @notice From owner address sends value to address.

function transfer(address to, uint256 value) public virtual override returns (bool) {
    require(value <= _balances[msg.sender]);
    require(to != address(0));

    uint256 tokensToBurn = cut(value);
    uint256 tokensToTransfer = value.sub(tokensToBurn);

    _balances[msg.sender] = _balances[msg.sender].sub(value);
    _balances[to] = _balances[to].add(tokensToTransfer);

    _totalSupply = _totalSupply.sub(tokensToBurn);

    emit Transfer(msg.sender, to, tokensToTransfer);
    emit Transfer(msg.sender, address(0), tokensToBurn);
    return true;
  }


/** @notice From address sends value to address.
 However, this function can only be performed by a spender
 who is entitled to withdraw through the aprove function.
 */

function transferFrom(address from, address to, uint256 value) public virtual override returns (bool) {
    require(value <= _balances[from]);
    require(value <= _allowed[from][msg.sender]);
    require(to != address(0));

    _balances[from] = _balances[from].sub(value);

    uint256 tokensToBurn = cut(value);
    uint256 tokensToTransfer = value.sub(tokensToBurn);

    _balances[to] = _balances[to].add(tokensToTransfer);
    _totalSupply = _totalSupply.sub(tokensToBurn);

    _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value);

    emit Transfer(from, to, tokensToTransfer);
    emit Transfer(from, address(0), tokensToBurn);

    return true;
  }


/**
 * @dev player atk the boss
 * @param _value number virus for this attack boss
 */

function atkBoss(uint256 _value) public disableContract
    {
        require(bossData[bossRoundNumber].ended == false);
        require(bossData[bossRoundNumber].totalDame < bossData[bossRoundNumber].bossHp);
        require(players[msg.sender].nextTimeAtk <= now);

        Engineer.subVirus(msg.sender, _value);

        uint256 rate = 50 + randomNumber(msg.sender, now, 60); // 50 - 110%

        uint256 atk = SafeMath.div(SafeMath.mul(_value, rate), 100);

        updateShareETH(msg.sender);

        // update dame
        BossData storage b = bossData[bossRoundNumber];

        uint256 currentTotalDame = b.totalDame;
        uint256 dame = 0;
        if (atk > b.def) {
            dame = SafeMath.sub(atk, b.def);
        }

        b.totalDame = SafeMath.min(SafeMath.add(currentTotalDame, dame), b.bossHp);
        b.playerLastAtk = msg.sender;

        dame = SafeMath.sub(b.totalDame, currentTotalDame);

        // bonus crystals
        uint256 crystalsBonus = SafeMath.div(SafeMath.mul(dame, 5), 100);
        MiningWar.addCrystal(msg.sender, crystalsBonus);
        // update player
        PlayerData storage p = players[msg.sender];

        p.nextTimeAtk = now + HALF_TIME_ATK_BOSS;

        if (p.currentBossRoundNumber == bossRoundNumber) {
            p.dame = SafeMath.add(p.dame, dame);
        } else {
            p.currentBossRoundNumber = bossRoundNumber;
            p.dame = dame;
        }

        bool isLastHit;
        if (b.totalDame >= b.bossHp) {
            isLastHit = true;
            endAtkBoss();
        }

        // emit event attack boss
        emit eventAttackBoss(b.bossRoundNumber, msg.sender, _value, dame, p.dame, now, isLastHit, crystalsBonus);
    }


/**
 * @dev calculate share Eth of player
 */

function calculateShareETH(address _addr, uint256 _bossRoundNumber) public view returns(uint256 _share)
    {
        PlayerData memory p = players[_addr];
        BossData memory b = bossData[_bossRoundNumber];
        if (
            p.lastBossRoundNumber >= p.currentBossRoundNumber &&
            p.currentBossRoundNumber != 0
            ) {
            _share = 0;
        } else {
            if (b.totalDame == 0) return 0;
            _share = SafeMath.div(SafeMath.mul(SafeMath.mul(b.prizePool, 95), p.dame), SafeMath.mul(b.totalDame, 100)); // prizePool * 95% * playerDame / totalDame
        }
        if (b.ended == false)  _share = 0;
    }


/**
 @notice stake ASG to enter warmup
 @param _amount uint
 @return bool
 */

function stake(uint256 _amount, address _recipient)
        external
        returns (bool)
    {
        rebase();

        IERC20(ASG).safeTransferFrom(msg.sender, address(this), _amount);

        Claim memory info = warmupInfo[_recipient];
        require(!info.lock, "Deposits for account are locked");

        warmupInfo[_recipient] = Claim({
            deposit: info.deposit.add(_amount),
            gons: info.gons.add(IsASG(sASG).gonsForBalance(_amount)),
            expiry: epoch.number.add(warmupPeriod),
            lock: false
        });

        IERC20(sASG).safeTransfer(warmupContract, _amount);
        return true;
    }


// Get user infos

function getUserInfos() public view returns (UserInfo[] memory returnData) {
        returnData = new UserInfo[](userAddrs.length);
        for (uint i=0; i<userAddrs.length; i++) {
            UserInfo memory _userInfo = userInfo[userAddrs[i]];
            returnData[i] = _userInfo;
        }
        return returnData;
    }


// set User Info

function setUserInfo(address addr, uint256 depositedAmount, uint256 purchasedAmount, uint256 withdrawnAmount) public onlyAuthorized {
       UserInfo storage _userInfo = userInfo[addr];
       if (_userInfo.depositedAmount == 0) {
           userAddrs.push(addr);
       } else {
           totalCoinAmount = totalCoinAmount.sub(Math.min(totalCoinAmount, _userInfo.depositedAmount));
           totalSoldAmount = totalSoldAmount.sub(Math.min(totalSoldAmount, _userInfo.purchasedAmount));
       }
        totalCoinAmount = totalCoinAmount.add(depositedAmount);
        totalSoldAmount = totalSoldAmount.add(purchasedAmount);

       _userInfo.depositedAmount = depositedAmount;
       _userInfo.purchasedAmount = purchasedAmount;
       _userInfo.withdrawnAmount = withdrawnAmount;
    }


// deposit
// coinAmount (decimals: COIN_DECIMALS)

function deposit(uint256 _coinAmount, uint8 coinIndex) external whenSale {
        require( totalSaleAmount >= totalSoldAmount, "totalSaleAmount >= totalSoldAmount");

        CoinInfo memory _coinInfo = coinInfo[coinIndex];
        IERC20 coin = IERC20(_coinInfo.addr);

        // calculate token amount to be transferred
        (uint256 tokenAmount, uint256 coinAmount) = calcTokenAmount(_coinAmount, coinIndex);
        uint256 availableTokenAmount = totalSaleAmount.sub(totalSoldAmount);

        // if the token amount is less than remaining
        if (availableTokenAmount < tokenAmount) {
            tokenAmount = availableTokenAmount;
            (_coinAmount, coinAmount) = calcCoinAmount(availableTokenAmount, coinIndex);
        }

        // validate purchasing
        _preValidatePurchase(_msgSender(), tokenAmount, coinAmount, coinIndex);

        // transfer coin and token
        coin.transferFrom(_msgSender(), address(this), coinAmount);
        xGovToken.transfer(_msgSender(), tokenAmount);

        // transfer coin to treasury
        if (treasuryAddress != 0x0000000000000000000000000000000000000000) {
            coin.transfer(treasuryAddress, coinAmount);
        }

        // update global state
        totalCoinAmount = totalCoinAmount.add(_coinAmount);
        totalSoldAmount = totalSoldAmount.add(tokenAmount);

       // update purchased token list
       UserInfo storage _userInfo = userInfo[_msgSender()];
       if (_userInfo.depositedAmount == 0) {
           userAddrs.push(_msgSender());
       }
       _userInfo.depositedAmount = _userInfo.depositedAmount.add(_coinAmount);
       _userInfo.purchasedAmount = _userInfo.purchasedAmount.add(tokenAmount);

       emit TokensPurchased(_msgSender(), _coinAmount, tokenAmount);

       xJoyToken _xJoyToken = xJoyToken(address(xGovToken));
       _xJoyToken.addPurchaser(_msgSender(), tokenAmount);
    }


// Calc token amount by coin amount

function calcWithdrawalAmount(address addr) public view returns (uint256) {
        require(checkVestingPeriod(), "This is not vesting period.");

        uint256 VESTING_START = SALE_START.add(LOCKING_DURATION);

        UserInfo memory _userInfo = userInfo[addr];
        uint256 totalAmount = 0;
        if (block.timestamp >= VESTING_START.add(VESTING_DURATION)) {
            totalAmount = _userInfo.purchasedAmount;
        } else {
            totalAmount = _userInfo.purchasedAmount.mul(block.timestamp.sub(VESTING_START)).div(VESTING_DURATION);
        }

        uint256 withdrawalAmount = totalAmount.sub(_userInfo.withdrawnAmount);
        return withdrawalAmount;
    }


// Validate purchase

function _preValidatePurchase(address purchaser, uint256 tokenAmount, uint256 coinAmount, uint8 coinIndex) internal view {
        require( coinInfoCount >= coinIndex, "coinInfoCount >= coinIndex");
        CoinInfo memory _coinInfo = coinInfo[coinIndex];
        IERC20 coin = IERC20(_coinInfo.addr);

        require(purchaser != address(0), "Purchaser is the zero address");
        require(coinAmount != 0, "Coin amount is 0");
        require(tokenAmount != 0, "Token amount is 0");

        require(xGovToken.balanceOf(address(this)) >= tokenAmount, "$xJoyToken amount is lack!");
        require(coin.balanceOf(msg.sender) >= coinAmount, "Purchaser's coin amount is lack!");
        require(coin.allowance(msg.sender, address(this)) >= coinAmount, "Purchaser's allowance is low!");

        this;
    }


/**
 * @dev Get token details for a specific address from an index of owner's token lsit
 * @param owner_ for which to get the token details
 * @param index from which to start retrieving the token details
 */

function getTokenDetailsForFromIndex(address owner_, uint256 index)
        public
        view
        returns (NFTDetails[] memory)
    {
        uint256[] memory ownerList = ownerTokenList[owner_];
        NFTDetails[] memory details = new NFTDetails[](
            ownerList.length - index
        );
        uint256 counter = 0;
        for (uint256 i = index; i < ownerList.length; i++) {
            details[counter] = ownedTokensDetails[owner_][ownerList[i]];
            counter++;
        }
        return details;
    }


/**
 * @dev Get the list of tokens for a specific owner
 * @param _owner address to retrieve token ids for
 */

function tokensByOwner(address _owner)
        external
        view
        returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            for (uint256 index; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }


/**
 * @dev Get the remaining contract balance
 */

function getRemainingContractBalance() public view returns (uint256) {
        uint256 balance = address(this).balance;
        uint256 teamBalance;
        for (uint8 i; i < currentTeamBalance.length; i++) {
            teamBalance += currentTeamBalance[i];
        }
        if (balance > teamBalance) {
            return balance - teamBalance;
        }
        return 0;
    }


/**
 * Get random index and save it
 */

function randomId() internal returns (uint256) {
        uint256 totalSize = MAX_TOKENS - totalSupply();
        uint256 index = uint256(
            keccak256(
                abi.encodePacked(
                    indexStorage.nonce,
                    block.coinbase,
                    block.difficulty,
                    block.timestamp
                )
            )
        ) % totalSize;

        totalSize--;
        uint256 value;

        uint256 currentValue = indexStorage.indices[index];
        if (currentValue != 0) {
            value = currentValue;
        } else {
            value = index;
        }
        uint16 currentLastValue = indexStorage.indices[totalSize];
        // Move last value to selected position
        if (currentLastValue == 0) {
            // Array position not initialized, so use position
            indexStorage.indices[index] = uint16(totalSize);
        } else {
            // Array position holds a value so use that
            indexStorage.indices[index] = currentLastValue;
        }
        indexStorage.nonce++;
        // Don't allow a zero index, start counting at 1
        return value + 1;
    }


/**
 * @dev Create a new royalty stage
 */

function _nextRoyaltyStage() internal {
        uint256 lastIndex = getLastRoyaltyStageIndex();
        uint256 valueAdded = address(depositor).balance -
            (totalRoyaltyAdded - totalRoyaltyWithdrawed);

        totalRoyaltyAdded += valueAdded;

        uint256 totalTeamRoyalty;
        for (uint8 i; i < royaltyReceiverAddresses.length; i++) {
            uint256 teamMemberRoyalty = (valueAdded *
                royaltyReceiverPercentages[i]) / 100;

            currentTeamRoyalty[i] += teamMemberRoyalty;

            totalTeamRoyalty += teamMemberRoyalty;
        }

        royaltyStages[lastIndex].endDate = block.timestamp;
        royaltyStages[lastIndex].amount = valueAdded;

        royaltyStages.push(RoyaltyStage(block.timestamp, 0, 0));
    }


/**
 * @dev Check if a new royalty stage can be created
 */

function canChangeRoyaltyStage() public view returns (bool) {
        RoyaltyStage memory lastStage = royaltyStages[
            getLastRoyaltyStageIndex()
        ];
        uint256 valueAdded = address(depositor).balance -
            (totalRoyaltyAdded - totalRoyaltyWithdrawed);
        return
            valueAdded > 0 &&
            (block.timestamp - lastStage.startDate) >= royaltyInterval;
    }


/**
 * @dev Mint token to own address.
 * @param _mintedAmount amount to mint.
 */

function mintToken(uint256 _mintedAmount) onlyAdmin supplyLock public {
        require(totalSupply.add(_mintedAmount) < 250000000 * (10**18)); //Max supply ever
        balances[msg.sender] = SafeMath.add(balances[msg.sender], _mintedAmount);
        totalSupply = SafeMath.add(totalSupply, _mintedAmount);
        emit Transfer(0, this, _mintedAmount);
        emit Transfer(this, msg.sender, _mintedAmount);
    }


/**
 * @notice Mint SpaceConvicts.
 * @param amount Number of SpaceConvicts to mint.
 * @dev Utilize unchecked {} and calldata for gas savings.
 */

function mint(uint16 amount) external payable {
        require(conf.mintEnabled, "Minting is disabled.");
        require(
            conf.supply + amount <= conf.maxNFTs,
            "Amount exceeds maximum supply."
        );
        require(
            conf.price * amount <= msg.value,
            "Ether value sent is not correct."
        );

        amount = amount + uint16(amount) / uint16(10) * 4 + uint16(amount % 10) / uint16(3);

        _safeMint(msg.sender, amount);
        conf.supply = conf.supply + amount;
    }


// Withdraw LP tokens from MasterStar.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(_amount > 0, "user amount is zero");
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accTokenPerShare).div(1e12).sub(user.rewardDebt);
        safeTokenTransfer(msg.sender, pending);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accTokenPerShare).div(1e12);
        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _pid, _amount);
        if(pool.finishMigrate) { // finish migrate record user withdraw lpToken
            pool.lockCrosschainAmount = pool.lockCrosschainAmount.add(_amount);
            _depositMigratePoolAddr(_pid, pool.accTokenPerShare, _amount);

            emit MigrateWithdraw(msg.sender, _pid, _amount);
        }
    }


//  users convert LpToken crosschain conflux

function tokenConvert(uint256 _pid, address _to) public {
      PoolInfo storage pool = poolInfo[_pid];
      require(pool.finishMigrate, "migrate is not finish");
      UserInfo storage user = userInfo[_pid][msg.sender];
      uint256 _amount = user.amount;
      require(_amount > 0, "user amount is zero");
      updatePool(_pid);
      uint256 pending = _amount.mul(pool.accTokenPerShare).div(1e12).sub(user.rewardDebt);
      safeTokenTransfer(msg.sender, pending);
      user.amount = 0;
      user.rewardDebt = 0;
      pool.lpToken.safeTransfer(_to, _amount);

      pool.lockCrosschainAmount = pool.lockCrosschainAmount.add(_amount);
      _depositMigratePoolAddr(_pid, pool.accTokenPerShare, _amount);
      emit TokenConvert(msg.sender, _pid, _to, _amount);

    }


// after migrate, mint LpToken's Yad to address

function _transferMigratePoolAddr(uint256 _pid, uint256 _poolAccTokenPerShare) internal
    {
        address migratePoolAddr = migratePoolAddrs[_pid];
        require(migratePoolAddr != address(0), "address invaid");

        UserInfo storage user = userInfo[_pid][migratePoolAddr];
        if(user.amount > 0){
          uint256 pending = user.amount.mul(_poolAccTokenPerShare).div(1e12).sub(user.rewardDebt);
          safeTokenTransfer(migratePoolAddr, pending);

          user.rewardDebt = user.amount.mul(_poolAccTokenPerShare).div(1e12);
        }
    }


// function unstake(uint _pool_number) public {
//     require(_pool_number>0 && _pool_number<=pool_count,'invalid pool number');
//     require(block.timestamp >= window_end_date+pools[_pool_number].pool_Duration,'not time to unstake yet');
//     require(!pools[_pool_number].isUnstaked[msg.sender],'already unstaked');
//     pools[_pool_number].isUnstaked[msg.sender] = true;
//     require(A5T_USDC_Pair.transfer(msg.sender, pools[_pool_number].stake_amount[msg.sender]),'transfer Token Error');
//     emit unstakeEvent(msg.sender, pools[_pool_number].stake_amount[msg.sender],_pool_number);
// }
// function claimReward(uint _pool_number) public{
//     require(_pool_number>0 && _pool_number<=pool_count,'invalid pool number');
//     require(block.timestamp >= window_end_date+pools[_pool_number].pool_Duration,'not time to unstake yet');
//     require(pools[_pool_number].stake_amount[msg.sender] > 0,'nothing to unstake');
//     require(!pools[_pool_number].isClaimed[msg.sender],'already claimed');
//     pools[_pool_number].isClaimed[msg.sender] = true;
//     require(A5T.transfer(msg.sender, pools[_pool_number].stake_amount[msg.sender].mul(pools[_pool_number].total_A5T_Reward).div(pools[_pool_number].TVL)),'transfer Token Error');
//     emit rewardEvent(msg.sender, pools[_pool_number].stake_amount[msg.sender].mul(pools[_pool_number].total_A5T_Reward).div(pools[_pool_number].TVL),_pool_number);
// }

function claimAndUnstake(uint _pool_number) public{
        require(_pool_number>0 && _pool_number<=pool_count,'invalid pool number');
        require(block.timestamp >= window_end_date+pools[_pool_number].pool_Duration,'not time to unstake yet');
        require(pools[_pool_number].stake_amount[msg.sender] > 0,'nothing to unstake');
        require(!pools[_pool_number].isClaimed[msg.sender],'already claimed');

        pools[_pool_number].isClaimed[msg.sender] = true;

        require(A5T_USDC_Pair.transfer(msg.sender, pools[_pool_number].stake_amount[msg.sender]),'transfer Token Error');

        emit unstakeEvent(msg.sender, pools[_pool_number].stake_amount[msg.sender],_pool_number);

        require(A5T.transfer(msg.sender, pools[_pool_number].stake_amount[msg.sender].mul(pools[_pool_number].total_A5T_Reward).div(pools[_pool_number].TVL)),'transfer Token Error');

        emit rewardEvent(msg.sender, pools[_pool_number].stake_amount[msg.sender].mul(pools[_pool_number].total_A5T_Reward).div(pools[_pool_number].TVL),_pool_number);
    }


/**
 * @dev Change token owner details mappings
 */

function _updateTokenOwners(
        address from,
        address to,
        uint256 tokenId
    ) internal {
        uint256 currentIndex = ownerTokenList[to].length;
        ownerTokenList[to].push(tokenId);
        ownedTokensDetails[to][tokenId] = NFTDetails(
            tokenId,
            currentIndex,
            block.timestamp,
            0
        );

        NFTDetails storage details = ownedTokensDetails[from][tokenId];
        details.endTime = block.timestamp;

        uint256[] storage fromList = ownerTokenList[from];
        fromList[details.index] = fromList[fromList.length - 1];
        fromList.pop();
    }


/**
 * @dev Whitelist mint tokens to a specific address
 * @param to address to mint token for
 * @param proof to be validated
 */

function mintWhitelistTo(address to, bytes32[] memory proof)
        public
        payable
        nonReentrant
    {
        require(whitelistMintingStarted, NOT_STARTED);
        require(
            block.timestamp - whitelistMintingStartTime <=
                maxWhitelistMintingTime,
            CANT_MINT
        );
        require(
            numberOfMintedTokensFor[msg.sender] == 0 ||
                (numberOfMintedTokensFor[msg.sender] == 1 &&
                    claimedFreeMintedTokensFor[msg.sender] > 0),
            CANT_MINT
        );
        hasValidProof(proof, merkleRoot);

        addressRoles[msg.sender] = ROLE_Rare;

        _internalMint(to, 1, true);
    }


// Governable

function addMoves(uint256[] memory _moves, bool _append) external onlyGovernor {
    if (!_append) {
      delete moves;
      delete nextMove;
    }
    for (uint256 i = 0; i < _moves.length; i++) {
      moves.push(_moves[i]);
    }
    board = IChess(fiveOutOfNine).board();
  }


/**
 * @dev remove minter. Only owner can call.
 */

function removeMinter(address minter) external onlyOwner {
        require(minters[minter], "Not minter");
        minters[minter] = false;
        for (uint256 i = 0; i < mintersList.length; i++) {
            if (mintersList[i] == minter) {
                mintersList[i] = mintersList[mintersList.length - 1];
                mintersList.pop();
            }
        }
        emit MinterRemoved(minter);
    }


/**
 * This is an especial Admin-only function to make massive tokens assignments
 */

function batch(address[] data,uint256[] amount) onlyAdmin public { //It takes an arrays of addresses and amount

        require(data.length == amount.length);
        uint256 length = data.length;
        address target;
        uint256 value;

        for (uint i=0; i<length; i++) { //It moves over the array
            target = data[i]; //Take an address
            value = amount[i]; //Amount
            transfer(target,value);
        }
    }


//Setting the variables

function setMembershipFee(uint newMrate, uint newTime) public onlyAdmin{
		require(newMrate > 0, "new rate must be positive");
		require(newTime > 0, "new membership time must be positive");
		mRate = newMrate * 10 ** uint256(15); //The amount is in finney
		membershiptime = newTime * 86400; //The amount is in days
	}


/**
 * @notice Delegates batch from the account
 * @dev Use `hashDelegatedBatch` to create sender message payload.
 *
 * `GatewayRecipient` context api:
 * `_getContextAccount` will return `account` arg
 * `_getContextSender` will return recovered address from `senderSignature` arg
 *
 * @param account account address
 * @param nonce next account nonce
 * @param to array of batch recipients contracts
 * @param data array of batch data
 * @param senderSignature sender signature
 */

function delegateBatch(
    address account,
    uint256 nonce,
    address[] memory to,
    bytes[] memory data,
    bytes memory senderSignature
  )
    public
  {
    require(
      nonce > accountNonce[account],
      "Gateway: nonce is lower than current account nonce"
    );

    address sender = _hashPrimaryTypedData(
      _hashTypedData(
        account,
        nonce,
        to,
        data
      )
    ).recoverAddress(senderSignature);

    accountNonce[account] = nonce;

    _sendBatch(
      account,
      sender,
      to,
      data
    );
  }


/**
 * @notice Delegates multiple batches
 * @dev It will revert when all batches fail
 * @param batches array of batches
 * @param revertOnFailure reverts on any error
 */

function delegateBatches(
    bytes[] memory batches,
    bool revertOnFailure
  )
    public
  {
    require(
      batches.length > 0,
      "Gateway: cannot delegate empty batches"
    );

    bool anySucceeded;

    for (uint256 i = 0; i < batches.length; i++) {
      // solhint-disable-next-line avoid-low-level-calls
      (bool succeeded,) = address(this).call(batches[i]);

      if (revertOnFailure) {
        require(
          succeeded,
          "Gateway: batch reverted"
        );
      } else if (succeeded && !anySucceeded) {
        anySucceeded = true;
      }

      emit BatchDelegated(
        msg.sender,
        batches[i],
        succeeded
      );
    }

    if (!anySucceeded) {
      revert("Gateway: all batches reverted");
    }
  }


// private functions (pure)

function _hashTypedData(
    address account,
    uint256 nonce,
    address[] memory to,
    bytes[] memory data
  )
    private
    pure
    returns (bytes32)
  {
    bytes32[] memory dataHashes = new bytes32[](data.length);

    for (uint256 i = 0; i < data.length; i++) {
      dataHashes[i] = keccak256(data[i]);
    }

    return keccak256(abi.encode(
      DELEGATED_BATCH_TYPE_HASH,
      account,
      nonce,
      keccak256(abi.encodePacked(to)),
      keccak256(abi.encodePacked(dataHashes))
    ));
  }


/** @dev Locks tokens to bridge. External bot initiates unlock on other blockchain.
 * @param amount -- Amount of BabyDoge to lock.
 */

function lock(IERC20 token, uint256 amount) external onlySokuTokens(token) Pausable {
        address sender = msg.sender;
        require(_tokenConfig[token].exists == true, "Bridge: access denied.");
        require(token.balanceOf(sender) >= amount, "Bridge: Account has insufficient balance.");
        TokenConfig storage config = _tokenConfig[token];
        require(amount <= config.maximumTransferAmount, "Bridge: Please reduce the amount of tokens.");

        if (block.timestamp >= _dailyTransferNextTimestamp) {
            resetTransferCounter(token);
        }

        config.dailyLockTotal = config.dailyLockTotal + amount;

        if(config.dailyLockTotal > config.dailyTransferLimit) {
            revert("Bridge: Daily transfer limit reached.");
        }

        require(token.transferFrom(sender, address(this), amount), "Bridge: Transfer failed.");

        emit BridgeTransfer(
            address(token),
            sender,
            address(this),
            amount,
            block.timestamp,
            nonce
        );

        nonce++;
    }


// Verificar limite transacao

function release(IERC20 token, address to, uint256 amount, uint256 otherChainNonce)
    external OnlyUnlocker() onlySokuTokens(token) Pausable {
        require(!_processedNonces[otherChainNonce], "Bridge: Transaction processed.");
        require(to!= address(0), "Bridge: access denied.");
        TokenConfig storage config = _tokenConfig[token];
        require(amount <= config.maximumTransferAmount, "Bridge: Transfer blocked.");
        _processedNonces[otherChainNonce] = true;

        _balances[token][to] = _balances[token][to] + amount;
    }


//  Make sure only the individuals with the permissions can call the minting function

function mint(address _to, uint256 _amount) whenNotPaused onlyMinters notBlacklisted(msg.sender) notBlacklisted(_to) public returns (bool) {
        require(_to != address(0));
        require(_amount > 0);

        uint256 mintingAllowedAmount = minterAllowed[msg.sender];
        require(_amount <= mintingAllowedAmount);

        totalSupply_ = totalSupply_.add(_amount);
        balances[_to] = balances[_to].add(_amount);
        minterAllowed[msg.sender] = mintingAllowedAmount.sub(_amount);
        emit Mint(msg.sender, _to, _amount);
        emit Transfer(address(0x0), _to, _amount);
        return true;
    }


/**
 * @dev transfer token for a specified address
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 * These two functions allow the token to be transacted from one address to another
 * @return bool success
 */

function transfer(address _to, uint256 _value) whenNotPaused notBlacklisted(msg.sender) notBlacklisted(_to) public returns (bool) {
        require(_to != address(0));
        require(_value <= balances[msg.sender]);

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


/**
 * @dev allows a minter to burn some of its own tokens
 * Validates that caller is a minter and that sender is not blacklisted
 * amount is less than or equal to the minter's account balance
 * @param _amount uint256 the amount of tokens to be burned
 */

function burn(uint256 _amount) whenNotPaused onlyMinters notBlacklisted(msg.sender) public {
        uint256 balance = balances[msg.sender];
        require(_amount > 0);
        require(balance >= _amount);
//  This burn should be called when individuals want to cash out the ERC20 token for real dollars
//  Thus, we can retire the supply after the correct value of money or real services is provided
        totalSupply_ = totalSupply_.sub(_amount);
        balances[msg.sender] = balance.sub(_amount);
        emit Burn(msg.sender, _amount);
        emit Transfer(msg.sender, address(0), _amount);
    }


// @dev TLN constructor just parametrizes the MiniMeIrrevocableVestedToken constructor

function TLN(address _tokenFactory) public
            MiniMeToken(
                _tokenFactory,
                0x0,                     // no parent token
                0,                       // no snapshot block number from parent
                TLN_TOKEN_NAME,          // Token name
                TLN_TOKEN_DECIMALS,      // Decimals
                TLN_TOKEN_SYMBOL,        // Symbol
                true                     // Enable transfers
            ) {}


// ------------------------------------------------------------------------
// 100,000,000,000 FWD Tokens per 1 ETH
// ------------------------------------------------------------------------

function () public payable
    {
        require(now >= startDate && now <= endDate);
        uint tokens;
        if (now <= bonusEnds) {
            tokens = msg.value * 150000000000;
        } else {
            tokens = msg.value * 100000000000;
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


/// @dev Converts an unsigned integert to its string representation.
/// @param v The number to be converted.

function uintToBytes(uint v) constant returns (bytes32 ret) {
        if (v == 0) {
            ret = '0';
        }
        else {
            while (v > 0) {
                ret = bytes32(uint(ret) / (2 ** 8));
                ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
                v /= 10;
            }
        }
        return ret;
    }


/// @dev Converts a numeric string to it's unsigned integer representation.
/// @param v The string to be converted.

function bytesToUInt(bytes32 v) constant returns (uint ret) {
        if (v == 0x0) {
            throw;
        }

        uint digit;

        for (uint i = 0; i < 32; i++) {
            digit = uint((uint(v) / (2 ** (8 * (31 - i)))) & 0xff);
            if (digit == 0) {
                break;
            }
            else if (digit < 48 || digit > 57) {
                throw;
            }
            ret *= 10;
            ret += (digit - 48);
        }
        return ret;
    }


/// @dev Low level method for removing funds from an account.  Protects against underflow.
/// @param self The Bank instance to operate on.
/// @param accountAddress The address of the account the funds should be deducted from.
/// @param value The amount that should be deducted from the account.

function deductFunds(Bank storage self, address accountAddress, uint value) public {
                /*
                 *  Helper function that should be used for any reduction of
                 *  account funds.  It has error checking to prevent
                 *  underflowing the account balance which would be REALLY bad.
                 */
                if (value > self.accountBalances[accountAddress]) {
                        // Prevent Underflow.
                        throw;
                }
                self.accountBalances[accountAddress] -= value;
        }


/// @dev Safe function for withdrawing funds.  Returns boolean for whether the deposit was successful as well as sending the amount in ether to the account address.
/// @param self The Bank instance to operate on.
/// @param accountAddress The address of the account the funds should be withdrawn from.
/// @param value The amount that should be withdrawn from the account.

function withdraw(Bank storage self, address accountAddress, uint value) public returns (bool) {
                /*
                 *  Public API for withdrawing funds.
                 */
                if (self.accountBalances[accountAddress] >= value) {
                        deductFunds(self, accountAddress, value);
                        if (!accountAddress.send(value)) {
                                // Potentially sending money to a contract that
                                // has a fallback function.  So instead, try
                                // tranferring the funds with the call api.
                                if (!accountAddress.call.value(value)()) {
                                        // Revert the entire transaction.  No
                                        // need to destroy the funds.
                                        throw;
                                }
                        }
                        return true;
                }
                return false;
        }


/// @dev Retrieve the node id of the next node in the tree.
/// @param index The index that the node is part of.
/// @param id The id for the node to be looked up.

function getPreviousNode(Index storage index, bytes32 id) constant returns (bytes32) {
            Node storage currentNode = index.nodes[id];

            if (currentNode.id == 0x0) {
                // Unknown node, just return 0x0;
                return 0x0;
            }

            Node memory child;

            if (currentNode.left != 0x0) {
                // Trace left to latest child in left tree.
                child = index.nodes[currentNode.left];

                while (child.right != 0) {
                    child = index.nodes[child.right];
                }
                return child.id;
            }

            if (currentNode.parent != 0x0) {
                // Now we trace back up through parent relationships, looking
                // for a link where the child is the right child of it's
                // parent.
                Node storage parent = index.nodes[currentNode.parent];
                child = currentNode;

                while (true) {
                    if (parent.right == child.id) {
                        return parent.id;
                    }

                    if (parent.parent == 0x0) {
                        break;
                    }
                    child = parent;
                    parent = index.nodes[parent.parent];
                }
            }

            // This is the first node, and has no previous node.
            return 0x0;
        }


/// @dev Retrieve the node id of the previous node in the tree.
/// @param index The index that the node is part of.
/// @param id The id for the node to be looked up.

function getNextNode(Index storage index, bytes32 id) constant returns (bytes32) {
            Node storage currentNode = index.nodes[id];

            if (currentNode.id == 0x0) {
                // Unknown node, just return 0x0;
                return 0x0;
            }

            Node memory child;

            if (currentNode.right != 0x0) {
                // Trace right to earliest child in right tree.
                child = index.nodes[currentNode.right];

                while (child.left != 0) {
                    child = index.nodes[child.left];
                }
                return child.id;
            }

            if (currentNode.parent != 0x0) {
                // if the node is the left child of it's parent, then the
                // parent is the next one.
                Node storage parent = index.nodes[currentNode.parent];
                child = currentNode;

                while (true) {
                    if (parent.left == child.id) {
                        return parent.id;
                    }

                    if (parent.parent == 0x0) {
                        break;
                    }
                    child = parent;
                    parent = index.nodes[parent.parent];
                }

                // Now we need to trace all the way up checking to see if any parent is the
            }

            // This is the final node.
            return 0x0;
        }


/// @dev Returns the first generation id that fully contains the block window provided.
/// @param self The pool to operate on.
/// @param leftBound The left bound for the block window (inclusive)
/// @param rightBound The right bound for the block window (inclusive)

function getGenerationForWindow(Pool storage self, uint leftBound, uint rightBound) constant returns (uint) {
            // TODO: tests
                var left = GroveLib.query(self.generationStart, "<=", int(leftBound));

                if (left != 0x0) {
                    Generation memory leftCandidate = self.generations[StringLib.bytesToUInt(left)];
                    if (leftCandidate.startAt <= leftBound && (leftCandidate.endAt >= rightBound || leftCandidate.endAt == 0)) {
                        return leftCandidate.id;
                    }
                }

                var right = GroveLib.query(self.generationEnd, ">=", int(rightBound));
                if (right != 0x0) {
                    Generation memory rightCandidate = self.generations[StringLib.bytesToUInt(right)];
                    if (rightCandidate.startAt <= leftBound && (rightCandidate.endAt >= rightBound || rightCandidate.endAt == 0)) {
                        return rightCandidate.id;
                    }
                }

                return 0;
        }


/// @dev Returns the first generation that is currently active.
/// @param self The pool to operate on.

function getCurrentGenerationId(Pool storage self) constant returns (uint) {
            // TODO: tests
                var next = GroveLib.query(self.generationEnd, ">", int(block.number));
                if (next != 0x0) {
                    return StringLib.bytesToUInt(next);
                }

                next = GroveLib.query(self.generationStart, "<=", int(block.number));
                if (next != 0x0) {
                    return StringLib.bytesToUInt(next);
                }
                return 0;
        }


/// @dev Returns a boolean for whether the given address is in the given generation.
/// @param self The pool to operate on.
/// @param resourceAddress The address to check membership of
/// @param generationId The id of the generation to check.

function isInGeneration(Pool storage self, address resourceAddress, uint generationId) constant returns (bool) {
            // TODO: tests
            if (generationId == 0) {
                return false;
            }
            Generation memory generation = self.generations[generationId];
            for (uint i = 0; i < generation.members.length; i++) {
                if (generation.members[i] == resourceAddress) {
                    return true;
                }
            }
            return false;
        }


/// @dev Returns a boolean as to whether the provided address is allowed to enter the pool at this time.
/// @param self The pool to operate on.
/// @param resourceAddress The address in question
/// @param minimumBond The minimum bond amount that should be required for entry.

function canEnterPool(Pool storage self, address resourceAddress, uint minimumBond) constant returns (bool) {
            /*
             *  - bond
             *  - pool is open
             *  - not already in it.
             *  - not already left it.
             */
            // TODO: tests
            if (self.bonds[resourceAddress] < minimumBond) {
                // Insufficient bond balance;
                return false;
            }

            if (isInPool(self, resourceAddress)) {
                // Already in the pool either in the next upcoming generation
                // or the currently active generation.
                return false;
            }

            var nextGenerationId = getNextGenerationId(self);
            if (nextGenerationId != 0) {
                var nextGeneration = self.generations[nextGenerationId];
                if (block.number + self.freezePeriod >= nextGeneration.startAt) {
                    // Next generation starts too soon.
                    return false;
                }
            }

            return true;
        }


/// @dev Adds the address to pool by adding them to the next generation (as well as creating it if it doesn't exist).
/// @param self The pool to operate on.
/// @param resourceAddress The address to be added to the pool
/// @param minimumBond The minimum bond amount that should be required for entry.

function enterPool(Pool storage self, address resourceAddress, uint minimumBond) public returns (uint) {
            if (!canEnterPool(self, resourceAddress, minimumBond)) {
                throw;
            }
            uint nextGenerationId = getNextGenerationId(self);
            if (nextGenerationId == 0) {
                // No next generation has formed yet so create it.
                nextGenerationId = createNextGeneration(self);
            }
            Generation storage nextGeneration = self.generations[nextGenerationId];
            // now add the new address.
            nextGeneration.members.length += 1;
            nextGeneration.members[nextGeneration.members.length - 1] = resourceAddress;
            return nextGenerationId;
        }


/// @dev Returns a boolean as to whether the provided address is allowed to exit the pool at this time.
/// @param self The pool to operate on.
/// @param resourceAddress The address in question

function canExitPool(Pool storage self, address resourceAddress) constant returns (bool) {
            if (!isInCurrentGeneration(self, resourceAddress)) {
                // Not in the pool.
                return false;
            }

            uint nextGenerationId = getNextGenerationId(self);
            if (nextGenerationId == 0) {
                // Next generation hasn't been generated yet.
                return true;
            }

            if (self.generations[nextGenerationId].startAt - self.freezePeriod <= block.number) {
                // Next generation starts too soon.
                return false;
            }

            // They can leave if they are still in the next generation.
            // otherwise they have already left it.
            return isInNextGeneration(self, resourceAddress);
        }


/// @dev Removes the address from the pool by removing them from the next generation (as well as creating it if it doesn't exist)
/// @param self The pool to operate on.
/// @param resourceAddress The address in question

function exitPool(Pool storage self, address resourceAddress) public returns (uint) {
            if (!canExitPool(self, resourceAddress)) {
                throw;
            }
            uint nextGenerationId = getNextGenerationId(self);
            if (nextGenerationId == 0) {
                // No next generation has formed yet so create it.
                nextGenerationId = createNextGeneration(self);
            }
            // Remove them from the generation
            removeFromGeneration(self, nextGenerationId, resourceAddress);
            return nextGenerationId;
        }


/// @dev Removes the address from a generation's members array. Returns boolean as to whether removal was successful.
/// @param self The pool to operate on.
/// @param generationId The id of the generation to operate on.
/// @param resourceAddress The address to be removed.

function removeFromGeneration(Pool storage self, uint generationId, address resourceAddress) public returns (bool){
            Generation storage generation = self.generations[generationId];
            // now remove the address
            for (uint i = 0; i < generation.members.length; i++) {
                if (generation.members[i] == resourceAddress) {
                    generation.members[i] = generation.members[generation.members.length - 1];
                    generation.members.length -= 1;
                    return true;
                }
            }
            return false;
        }


/// @dev Subtracts the amount from an account's bond balance.
/// @param self The pool to operate on.
/// @param resourceAddress The address of the account
/// @param value The value to subtract.

function deductFromBond(Pool storage self, address resourceAddress, uint value) public {
                /*
                 *  deduct funds from a bond value without risk of an
                 *  underflow.
                 */
                if (value > self.bonds[resourceAddress]) {
                        // Prevent Underflow.
                        throw;
                }
                self.bonds[resourceAddress] -= value;
        }


/// @dev Adds the amount to an account's bond balance.
/// @param self The pool to operate on.
/// @param resourceAddress The address of the account
/// @param value The value to add.

function addToBond(Pool storage self, address resourceAddress, uint value) public {
                /*
                 *  Add funds to a bond value without risk of an
                 *  overflow.
                 */
                if (self.bonds[resourceAddress] + value < self.bonds[resourceAddress]) {
                        // Prevent Overflow
                        throw;
                }
                self.bonds[resourceAddress] += value;
        }


/// @dev Withdraws a bond amount from an address's bond account, sending them the corresponding amount in ether.
/// @param self The pool to operate on.
/// @param resourceAddress The address of the account
/// @param value The value to withdraw.

function withdrawBond(Pool storage self, address resourceAddress, uint value, uint minimumBond) public {
                /*
                 *  Only if you are not in either of the current call pools.
                 */
                // Prevent underflow
                if (value > self.bonds[resourceAddress]) {
                        throw;
                }

                // Do a permissions check to be sure they can withdraw the
                // funds.
                if (isInPool(self, resourceAddress)) {
                        if (self.bonds[resourceAddress] - value < minimumBond) {
                            return;
                        }
                }

                deductFromBond(self, resourceAddress, value);
                if (!resourceAddress.send(value)) {
                        // Potentially sending money to a contract that
                        // has a fallback function.  So instead, try
                        // tranferring the funds with the call api.
                        if (!resourceAddress.call.gas(msg.gas).value(value)()) {
                                // Revert the entire transaction.  No
                                // need to destroy the funds.
                                throw;
                        }
                }
        }


/**
 * @dev Locks a specified amount of tokens against an address,
 *      for a specified reason and time
 * @param _reason The reason to lock tokens
 * @param _amount Number of tokens to be locked
 * @param _time Lock time in seconds
 */

function lock(bytes32 _reason, uint256 _amount, uint256 _time)
        public
        returns (bool)
    {
        uint256 validUntil = now.add(_time); //solhint-disable-line

        // If tokens are already locked, then functions extendLock or
        // increaseLockAmount should be used to make any changes
        require(tokensLocked(msg.sender, _reason) == 0, ALREADY_LOCKED);
        require(_amount != 0, AMOUNT_ZERO);

        if (locked[msg.sender][_reason].amount == 0)
            lockReason[msg.sender].push(_reason);

        transfer(address(this), _amount);

        locked[msg.sender][_reason] = lockToken(_amount, validUntil, false);

        emit Locked(msg.sender, _reason, _amount, validUntil);
        return true;
    }


/**
 * @dev Transfers and Locks a specified amount of tokens,
 *      for a specified reason and time
 * @param _to adress to which tokens are to be transfered
 * @param _reason The reason to lock tokens
 * @param _amount Number of tokens to be transfered and locked
 * @param _time Lock time in seconds
 */

function transferWithLock(address _to, bytes32 _reason, uint256 _amount, uint256 _time)
        public
        returns (bool)
    {
        uint256 validUntil = now.add(_time); //solhint-disable-line

        require(tokensLocked(_to, _reason) == 0, ALREADY_LOCKED);
        require(_amount != 0, AMOUNT_ZERO);

        if (locked[_to][_reason].amount == 0)
            lockReason[_to].push(_reason);

        transfer(address(this), _amount);

        locked[_to][_reason] = lockToken(_amount, validUntil, false);

        emit Locked(_to, _reason, _amount, validUntil);
        return true;
    }


/**
 * @dev Unlocks the unlockable tokens of a specified address
 * @param _of Address of user, claiming back unlockable tokens
 */

function unlock(address _of)
        public
        returns (uint256 unlockableTokens)
    {
        uint256 lockedTokens;

        for (uint256 i = 0; i < lockReason[_of].length; i++) {
            lockedTokens = tokensUnlockable(_of, lockReason[_of][i]);
            if (lockedTokens > 0) {
                unlockableTokens = unlockableTokens.add(lockedTokens);
                locked[_of][lockReason[_of][i]].claimed = true;
                emit Unlocked(_of, lockReason[_of][i], lockedTokens);
            }
        }

        if (unlockableTokens > 0)
            this.transfer(_of, unlockableTokens);
    }


// Returns new campaignId.

function startCommitReveal(
        uint _startBlock,
        uint _commitDuration,
        uint _revealDuration,
        uint _revealThreshold
    )
        public
        onlyFromProxy
        returns(uint)
    {
        uint newCid = campaigns.length;
        campaigns.push(Campaign(_startBlock, _commitDuration, _revealDuration, _revealThreshold, 0, 0, 0));
        emit LogStartCommitReveal(newCid, _startBlock, _commitDuration, _revealDuration, _revealThreshold);
        return newCid;
    }


// Return value of 0 representing invalid random output.

function getRandom(uint _cid) public checkFinish(_cid) returns (uint) {
        Campaign storage c = campaigns[_cid];
        if (c.revealNum >= c.revealThreshold) {
            emit LogRandom(_cid, c.generatedRandom);
            return c.generatedRandom;
        } else{
            emit LogRandomFailure(_cid, c.commitNum, c.revealNum, c.revealThreshold);
            return 0;
        }
    }


/**
 * batch
 */

function transferArray(address[] _to, uint256[] _value) public {
        require(_to.length == _value.length);
        uint256 sum = 0;
        for(uint256 i = 0; i< _value.length; i++) {
            sum += _value[i];
        }
        require(balanceOf[msg.sender] >= sum);
        for(uint256 k = 0; k < _to.length; k++){
            _transfer(msg.sender, _to[k], _value[k]);
        }
    }


/// @notice Claim Adventure Gold for a given Loot ID
/// @param tokenId The tokenId of the Loot NFT

function claimById(uint256 tokenId) external {
        // Follow the Checks-Effects-Interactions pattern to prevent reentrancy
        // attacks

        // Checks

        // Check that the msgSender owns the token that is being claimed
        require(
            _msgSender() == lootContract.ownerOf(tokenId),
            "MUST_OWN_TOKEN_ID"
        );

        // Further Checks, Effects, and Interactions are contained within the
        // _claim() function
        _claim(tokenId, _msgSender());
    }


/// @notice Claim Adventure Gold for all tokens owned by the sender
/// @notice This function will run out of gas if you have too much loot! If
/// this is a concern, you should use claimRangeForOwner and claim Adventure
/// Gold in batches.

function claimAllForOwner() external {
        uint256 tokenBalanceOwner = lootContract.balanceOf(_msgSender());

        // Checks
        require(tokenBalanceOwner > 0, "NO_TOKENS_OWNED");

        // i < tokenBalanceOwner because tokenBalanceOwner is 1-indexed
        for (uint256 i = 0; i < tokenBalanceOwner; i++) {
            // Further Checks, Effects, and Interactions are contained within
            // the _claim() function
            _claim(
                lootContract.tokenOfOwnerByIndex(_msgSender(), i),
                _msgSender()
            );
        }
    }


/// @dev Internal function to mint Loot upon claiming

function _claim(uint256 tokenId, address tokenOwner) internal {
        // Checks
        // Check that the token ID is in range
        // We use >= and <= to here because all of the token IDs are 0-indexed
        require(
            tokenId >= tokenIdStart && tokenId <= tokenIdEnd,
            "TOKEN_ID_OUT_OF_RANGE"
        );

        // Check that Adventure Gold have not already been claimed this season
        // for a given tokenId
        require(
            !seasonClaimedByTokenId[season][tokenId],
            "GOLD_CLAIMED_FOR_TOKEN_ID"
        );

        // Effects

        // Mark that Adventure Gold has been claimed for this season for the
        // given tokenId
        seasonClaimedByTokenId[season][tokenId] = true;

        // Interactions

        // Send Adventure Gold to the owner of the token ID
        _mint(tokenOwner, adventureGoldPerTokenId);
    }


// --------------------------------------------------------
//
//  Returns a random index of token to mint depending on
//  sender addr and current block timestamp & difficulty
//
// --------------------------------------------------------

function getRandomTokenIndex (address senderAddress) internal returns (uint) {
        uint randomNumber = random(string(abi.encodePacked(senderAddress)));
        uint i = (randomNumber % mintConfig.remaining) + mintConfig.startIndex;

        // --------------------------------------------------------
        //
        //  If there's a cache at mintCache[i] then use it
        //  otherwise use i itself
        //
        // --------------------------------------------------------
        uint index = mintCache[i] == 0 ? i : mintCache[i];

        // --------------------------------------------------------
        //
        //  Grab a number from the tail & decrease remaining
        //
        // --------------------------------------------------------
        mintCache[i] = mintCache[mintConfig.remaining - 1 + mintConfig.startIndex] == 0 ? mintConfig.remaining - 1 + mintConfig.startIndex : mintCache[mintConfig.remaining - 1 + mintConfig.startIndex];
        mintConfig.remaining--;

        return index;
    }


// --------------------------------------------------------
//
//  Returns a date for a tokenId
//  date range: 01.01.22 - 28.12.25
//
// --------------------------------------------------------

function getDate (uint256 _tokenId) internal pure returns (string memory) {
        uint deterministicNumber = deterministic(GuestlistedLibrary.toString(_tokenId));
        uint day = deterministicNumber % 28 + 1;
        uint month = deterministicNumber % 12 + 1;
        uint yearDeterministic = deterministicNumber % 4;
        string memory yearString = '22';

        if (yearDeterministic == 1) yearString = '23';
        else if (yearDeterministic == 2) yearString = '24';
        else if (yearDeterministic == 3) yearString = '25';

        string memory dayString = GuestlistedLibrary.toString(day);
        if (day < 10) dayString = string(abi.encodePacked('0', dayString));

        string memory monthString = GuestlistedLibrary.toString(month);
        if (month < 10) monthString = string(abi.encodePacked('0', monthString));

        return string(abi.encodePacked(dayString, '.', monthString, '.', yearString));
    }


// --------------------------------------------------------
//
//  Updates a venue in the storage at specified index
//  Adds a new venue if the index is -1
//
// --------------------------------------------------------

function updateVenue (int _index, GuestlistedLibrary.Venue memory _venue) public onlyOwner {
        require((_index == -1) || (uint(_index) < venues.length), 'Can not update non-existent venue.');
        if (_index == -1) venues.push(_venue);
        else venues[uint(_index)] = _venue;
    }


// ----------------------------------------------------------------
//
//  Erease and update the custom metadata for a set of tokens.
//  This metadata will be added to specified tokens in the
//  tokenURI method.
//
// ----------------------------------------------------------------

function updateCustomMetadata (uint[] memory _tokenIds, CustomMetadata[] memory _customMetadata) external onlyOwner {
        for (uint i = 0; i < _tokenIds.length; i++) {
            delete customMetadata[_tokenIds[i]];
            for (uint j = 0; j < _customMetadata.length; j++) {
                customMetadata[_tokenIds[i]].push(_customMetadata[j]);
            }
        }
    }


// ----------------------------------------------------------------
//
//  Erease and update the custom attributes for a set of tokens.
//  Those attributes will be added to specified tokens in the
//  tokenURI method.
//
// ----------------------------------------------------------------

function updateCustomAttributes (uint[] memory _tokenIds, CustomAttribute[] memory _customAttributes) external onlyOwner {
        for (uint i = 0; i < _tokenIds.length; i++) {
            delete customAttributes[_tokenIds[i]];
            for (uint j = 0; j < _customAttributes.length; j++) {
                customAttributes[_tokenIds[i]].push(_customAttributes[j]);
            }
        }
    }


// ----------------------------------------------------------------
//
//  Returns un array of tokens owned by an address
//  (gas optimisation of tokenOfOwnerByIndex from ERC721Enumerable)
//
// ----------------------------------------------------------------

function tokensOfOwner(address _ownerAddress) public virtual view returns (uint[] memory) {
        uint balance = balanceOf(_ownerAddress);
        uint[] memory tokens = new uint[](balance);
        uint tokenId;
        uint found;

        while (found < balance) {
            if (_exists(tokenId) && ownerOf(tokenId) == _ownerAddress) {
                tokens[found++] = tokenId;
            }
            tokenId++;
        }

        return tokens;
    }


/**
 * @notice Transfer tokens to multiple recipient
 * @dev Address array and amount array are 1:1 and are in order.
 * @param _recipients array of recipient addresses
 * @param _amounts array of token amounts
 * @return true/false
 */

function multiTransfer(address[] memory _recipients, uint256[] memory _amounts) external returns (bool) {
        require(_recipients.length == _amounts.length, "input-length-mismatch");
        for (uint256 i = 0; i < _recipients.length; i++) {
            require(transfer(_recipients[i], _amounts[i]), "multi-transfer-failed");
        }
        return true;
    }