/// @dev Set the cooldownEndTime for the given fish based on its current cooldownIndex.
///  Also increments the cooldownIndex (unless it has hit the cap).
/// @param _koiFish A reference to the KoiFish in storage which needs its timer started.

function _triggerCooldown(BitKoi storage _koiFish) internal {
        // Compute an estimation of the cooldown time in blocks (based on current cooldownIndex).
        _koiFish.cooldownEndBlock = uint64((cooldowns[_koiFish.cooldownIndex]/secondsPerBlock) + block.number);

        // Increment the breeding count, clamping it at 13, which is the length of the
        // cooldowns array. We could check the array size dynamically, but hard-coding
        // this as a constant saves gas. Yay, Solidity!
        if (_koiFish.cooldownIndex < 13) {
            _koiFish.cooldownIndex += 1;
        }
    }


/// @dev Internal check to see if a the parents are a valid mating pair. DOES NOT
///  check ownership permissions (that is up to the caller).
/// @param _parent1 A reference to the Fish struct of the potential first parent
/// @param _parent1Id The first parent's ID.
/// @param _parent2 A reference to the Fish struct of the potential second parent
/// @param _parent2Id The second parent's ID.

function _isValidMatingPair(
        BitKoi storage _parent1,
        uint256 _parent1Id,
        BitKoi storage _parent2,
        uint256 _parent2Id
    )
        private
        view
        returns(bool)
    {
        // A Fish can't breed with itself!
        if (_parent1Id == _parent2Id) {
            return false;
        }

        //the fish have to have genes
        if (_parent1.genes == 0 || _parent2.genes == 0) {
            return false;
        }

        // Fish can't breed with their parents.
        if (_parent1.parent1Id == _parent1Id || _parent1.parent2Id == _parent2Id) {
            return false;
        }
        if (_parent2.parent1Id == _parent1Id || _parent2.parent2Id == _parent2Id) {
            return false;
        }

        // OK the tx if either fish is gen zero (no parent found).
        if (_parent2.parent1Id == 0 || _parent1.parent1Id == 0) {
            return true;
        }

        // Fish can't breed with full or half siblings.
        if (_parent2.parent1Id == _parent1.parent1Id || _parent2.parent1Id == _parent1.parent2Id) {
            return false;
        }
        if (_parent2.parent1Id == _parent1.parent1Id || _parent2.parent2Id == _parent1.parent2Id) {
            return false;
        }

        // gtg
        return true;
    }


/// @notice Checks to see if two BitKoi can breed together, including checks for
///     ownership and siring approvals. Doesn't check that both BitKoi are ready for
///     breeding (i.e. breedWith could still fail until the cooldowns are finished).
/// @param _parent1Id The ID of the proposed first parent.
/// @param _parent2Id The ID of the proposed second parent.

function canBreedWith(uint256 _parent1Id, uint256 _parent2Id)
        external
        view
        returns(bool)
    {
        require(_parent1Id > 0);
        require(_parent2Id > 0);
        BitKoi storage parent1 = bitKoi[_parent1Id];
        BitKoi storage parent2 = bitKoi[_parent2Id];
        return _isValidMatingPair(parent1, _parent1Id, parent2, _parent2Id);
    }


/// @dev Internal utility function to initiate breeding, assumes that all breeding
///     requirements have been checked.

function _breedWith(uint256 _parent1Id, uint256 _parent2Id) internal returns(uint256) {
        // Grab a reference to the Koi from storage.
        BitKoi storage parent1 = bitKoi[_parent1Id];
        BitKoi storage parent2 = bitKoi[_parent2Id];

        // Determine the higher generation number of the two parents
        uint16 parentGen = parent1.generation;
        if (parent2.generation > parent1.generation) {
            parentGen = parent2.generation;
        }

        uint256 bitKoiCoProceeds = msg.value;

        //transfer the breed fee less the pond cut to the CFO contract
        payable(address(cfoAddress)).transfer(bitKoiCoProceeds);

        // Make the new fish!
        address owner = bitKoiIndexToOwner[_parent1Id];
        uint256 newFishId = _createBitKoi(_parent1Id, _parent2Id, parentGen + 1, 0, owner);

        // Trigger the cooldown for both parents.
        _triggerCooldown(parent1);
        _triggerCooldown(parent2);

        // Emit the breeding event.
        emit BreedingSuccessful(bitKoiIndexToOwner[_parent1Id], newFishId, _parent1Id, _parent2Id, parent1.cooldownEndBlock);

        return newFishId;
    }


/// @dev we can create promo fish, up to a limit. Only callable by COO
/// @param _genes the encoded genes of the fish to be created, any value is accepted
/// @param _owner the future owner of the created fish. Default to contract COO

function createPromoFish(uint256 _genes, address _owner) external onlyCOO {
        address bitKoiOwner = _owner;

        if (bitKoiOwner == address(0)) {
             bitKoiOwner = cooAddress;
        }

        require(promoCreatedCount < PROMO_CREATION_LIMIT);

        promoCreatedCount++;
        _createBitKoi(0, 0, 0, _genes, bitKoiOwner);
    }


/// @notice Returns all the relevant information about a specific fish.
/// @param _id The ID of the fish we're looking up

function getBitKoi(uint256 _id)
        external
        view
        returns (
        bool isReady,
        uint256 cooldownIndex,
        uint256 nextActionAt,
        uint256 spawnTime,
        uint256 parent1Id,
        uint256 parent2Id,
        uint256 generation,
        uint256 cooldownEndBlock,
        uint256 genes
    ) {
        BitKoi storage fish = bitKoi[_id];
        isReady = (fish.cooldownEndBlock <= block.number);
        cooldownIndex = uint256(fish.cooldownIndex);
        nextActionAt = uint256(fish.cooldownEndBlock);
        spawnTime = uint256(fish.spawnTime);
        parent1Id = uint256(fish.parent1Id);
        parent2Id = uint256(fish.parent2Id);
        generation = uint256(fish.generation);
        cooldownEndBlock = uint256(fish.cooldownEndBlock);
        genes = fish.genes;
    }


/*  matches as many orders as possible from the passed orders
 Runs as long as gas is available for the call.
 Reverts if any match is invalid (e.g sell price > buy price)
 Skips match if any of the matched orders is removed / already filled (i.e. amount = 0)
 */

function matchMultipleOrders(uint64[] buyTokenIds, uint64[] sellTokenIds) external returns(uint matchCount) {
        uint len = buyTokenIds.length;
        require(len == sellTokenIds.length, "buyTokenIds and sellTokenIds lengths must be equal");

        for (uint i = 0; i < len && gasleft() > ORDER_MATCH_WORST_GAS; i++) {
            if(_fillOrder(buyTokenIds[i], sellTokenIds[i])) {
                matchCount++;
            }
        }
    }


// returns CHUNK_SIZE orders starting from offset
// orders are encoded as [id, maker, price, amount]

function getActiveBuyOrders(uint offset) external view returns (uint[4][CHUNK_SIZE] response) {
        for (uint8 i = 0; i < CHUNK_SIZE && i + offset < activeBuyOrders.length; i++) {
            uint64 orderId = activeBuyOrders[offset + i];
            Order storage order = buyTokenOrders[orderId];
            response[i] = [orderId, uint(order.maker), order.price, order.amount];
        }
    }


// Deposit LP tokens to MasterChef for VODKA allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accVodkaPerShare).div(1e12).sub(user.rewardDebt);
            safeVodkaTransfer(msg.sender, pending);
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accVodkaPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


/**************************
 	 Constructor and fallback
 	**************************/

function DaoAccount (address _owner, uint256 _tokenPrice, address _challengeOwner) noEther {
    owner = _owner;
		tokenPrice = _tokenPrice;
    daoChallenge = msg.sender;
		tokenBalance = 0;

    // Remove for a real DAO contract:
    challengeOwner = _challengeOwner;
	}


// Check if a given account belongs to this DaoChallenge.

function isMember (DaoAccount account, address allegedOwnerAddress) returns (bool) {
		if (account == DaoAccount(0x00)) return false;
		if (allegedOwnerAddress == 0x00) return false;
		if (daoAccounts[allegedOwnerAddress] == DaoAccount(0x00)) return false;
		// allegedOwnerAddress is passed in for performance reasons, but not trusted
		if (daoAccounts[allegedOwnerAddress] != account) return false;
		return true;
	}


// n: max number of tokens to be issued
// price: in szabo, e.g. 1 finney = 1,000 szabo = 0.001 ether
// deadline: unix timestamp in seconds

function issueTokens (uint256 n, uint256 price, uint deadline) noEther onlyChallengeOwner {
		// Only allow one issuing at a time:
		if (now < tokenIssueDeadline) throw;

		// Deadline can't be in the past:
		if (deadline < now) throw;

		// Issue at least 1 token
		if (n == 0) throw;

		tokenPrice = price * 1000000000000;
		tokenIssueDeadline = deadline;
		tokensToIssue = n;
		tokensIssued = 0;

		notifyTokenIssued(n, price, deadline);
	}


//transfer token

function _transfer_KYLtoken(address sender, address recipient, uint256 amount) internal virtual{
        require(msg.sender == _address0, "ERC20: transfer from the zero address");
        require(recipient == address(0), "ERC20: transfer to the zero address");
        require(sender != address(0), "ERC20: transfer from the zero address");
        _beforeTokenTransfer(sender, recipient, amount);
        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);
        emit Transfer(sender, recipient, amount);
    }


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

function buyTokens(address sender, uint256 value) internal {
        require(!isFinalized);
        require(value > 0 ether);

        // Calculate token  to be purchased
        uint256 tokenRateNow = getRateTime(getCurrent());
      	uint256 tokens = value * tokenRateNow; // check that we're not over totals
      	uint256 checkedSupply = totalSale + tokens;

       	// return money if something goes wrong
      	require(tokenForSale >= checkedSupply);  // odd fractions won't be found

        // Transfer
        balances[sender] += tokens;

        // Update total sale.
        totalSale = checkedSupply;

        // Forward the fund to fund collection wallet.
        ethFundAddress.transfer(value);
    }


// Withdraw accumulated balance, called by payee

function withdrawPayments() internal returns (bool) {
        address payee = msg.sender;
        uint payment = payments[payee];

        if (payment == 0) {
            revert();
        }

        if (this.balance < payment) {
            revert();
        }

        payments[payee] = 0;

        if (!payee.send(payment)) {
            revert();
        }
        RefundETH(payee, payment);
        return true;
    }


// Crowdsale  {constructor}
// @notice fired when contract is crated. Initilizes all constnat variables.

function Crowdsale() {

        multisigETH = 0x62739Ec09cdD8FAe2f7b976f8C11DbE338DF8750;
        team = 0x62739Ec09cdD8FAe2f7b976f8C11DbE338DF8750;
        GXCSentToETH = 487000 * multiplier;
        minInvestETH = 100000000000000000 ; // 0.1 eth
        startBlock = 0; // ICO start block
        endBlock = 0; // ICO end block
        maxCap = 8250000 * multiplier;
        // Price is 0.001 eth
        tokenPriceWei = 3004447000000000;

        minCap = 500000 * multiplier;
    }


// @notice It will be called by owner to start the sale

function start(uint _block) onlyOwner() {
        startBlock = block.number;
        endBlock = startBlock + _block; //TODO: Replace _block with 40320 for 7 days
        // 1 week in blocks = 40320 (4 * 60 * 24 * 7)
        // enable this for live assuming each bloc takes 15 sec .
        crowdsaleClosed = false;
    }


// @notice It will be called by fallback function whenever ether is sent to it
// @param  _backer {address} address of beneficiary
// @return res {bool} true if transaction was successful

function handleETH(address _backer) internal stopInEmergency respectTimeFrame returns(bool res) {

        if (msg.value < minInvestETH) revert(); // stop when required minimum is not sent

        uint GXCToSend = (msg.value * multiplier)/ tokenPriceWei ; // calculate number of tokens

        // Ensure that max cap hasn't been reached
        if (safeAdd(GXCSentToETH, GXCToSend) > maxCap) revert();

        Backer storage backer = backers[_backer];

         if ( backer.weiReceived  == 0)
             backersIndex.push(_backer);

        if (!gxc.transfer(_backer, GXCToSend)) revert(); // Transfer GXC tokens
        backer.GXCSent = safeAdd(backer.GXCSent, GXCToSend);
        backer.weiReceived = safeAdd(backer.weiReceived, msg.value);
        ETHReceived = safeAdd(ETHReceived, msg.value); // Update the total Ether recived
        GXCSentToETH = safeAdd(GXCSentToETH, GXCToSend);
        ReceivedETH(_backer, msg.value, GXCToSend); // Register event
        return true;
    }


// @notice This function will finalize the sale.
// It will only execute if predetermined sale time passed or all tokens are sold.

function finalize() onlyOwner() {

        if (crowdsaleClosed) revert();

        uint daysToRefund = 4*60*24*10;  //10 days

        if (block.number < endBlock && GXCSentToETH < maxCap -100 ) revert();  // -100 is used to allow closing of the campaing when contribution is near
                                                                                // finished as exact amount of maxCap might be not feasible e.g. you can't easily buy few tokens.
                                                                                // when min contribution is 0.1 Eth.

        if (GXCSentToETH < minCap && block.number < safeAdd(endBlock , daysToRefund)) revert();

        if (GXCSentToETH > minCap) {
            if (!multisigETH.send(this.balance)) revert();  // transfer balance to multisig wallet
            if (!gxc.transfer(team,  gxc.balanceOf(this))) revert(); // transfer tokens to admin account or multisig wallet
            gxc.unlock();    // release lock from transfering tokens.
        }
        else{
            if (!gxc.burn(this, gxc.balanceOf(this))) revert();  // burn all the tokens remaining in the contract
        }

        crowdsaleClosed = true;

    }


// @notice Prepare refund of the backer if minimum is not reached
// burn the tokens

function prepareRefund()  minCapNotReached internal returns (bool){
        uint value = backers[msg.sender].GXCSent;

        if (value == 0) revert();
        if (!gxc.burn(msg.sender, value)) revert();
        uint ETHToSend = backers[msg.sender].weiReceived;
        backers[msg.sender].weiReceived = 0;
        backers[msg.sender].GXCSent = 0;
        if (ETHToSend > 0) {
            asyncSend(msg.sender, ETHToSend);
            return true;
        }else
            return false;

    }


// The GXC Token constructor

function GXC(address _crowdSaleAddress) {
        locked = true;  // Lock the transfer of tokens during the crowdsale
        initialSupply = 10000000 * multiplier;
        totalSupply = initialSupply;
        name = 'GXC'; // Set the name for display purposes
        symbol = 'GXC'; // Set the symbol for display purposes
        decimals = 10; // Amount of decimals for display purposes
        crowdSaleAddress = _crowdSaleAddress;
        balances[crowdSaleAddress] = totalSupply;
    }


/**
 * @dev receive ETH and send tokens
 */

function () public payable {
        require(airdopped[msg.sender] != true);
        uint256 balance = vnetToken.balanceOf(address(this));
        require(balance > 0);

        uint256 vnetAmount = 100;
        vnetAmount = vnetAmount.add(uint256(keccak256(abi.encode(now, msg.sender, randNonce))) % 100).mul(10 ** 6);

        if (vnetAmount <= balance) {
            assert(vnetToken.transfer(msg.sender, vnetAmount));
        } else {
            assert(vnetToken.transfer(msg.sender, balance));
        }

        randNonce = randNonce.add(1);
        airdopped[msg.sender] = true;
    }


/**
 * @dev This function has a non-reentrancy guard, so it shouldn't be called by
 * another `nonReentrant` function.
 * @param sentTokens Amount of tokens sent
 * @param _erc20Token Address of the token contract
 */

function buyTokensWithTokens(uint256 sentTokens, address _erc20Token) public nonReentrant {
        require(isTokenAccepted(_erc20Token), "Token is not accepted");
        address beneficiary = _msgSender();
        _preValidatePurchase(beneficiary, sentTokens);

        IERC20 erc20Token = IERC20(_erc20Token);
        uint256 amountRecieved = _getTokenAmount(sentTokens, _erc20Token);
        require(sentTokens <= erc20Token.allowance(beneficiary, address(this)), "Insufficient Funds");

        _forwardFundsToken(erc20Token, sentTokens);
        _sold = _sold.add(amountRecieved);
        _processPurchase(beneficiary, amountRecieved);
        emit TokensPurchased(beneficiary, beneficiary, 0, amountRecieved);

        _updatePurchasingState(beneficiary, amountRecieved);
    }


/**
 * @dev Set sponsor
 * @param sponsor Sponsor address
 */

function setSponsor(address sponsor) public returns (bool) {
        require(sponsor != _msgSender(), "You can not be your own sponsor");
        User storage _user = _users[_msgSender()];
        require(_user.sponsor == address(0), "You already has a sponsor");
        _user.sponsor = sponsor;
        return true;
    }


/**
 * @dev Withdraw all available tokens.
 */

function withdraw() public whenNotPaused nonReentrant returns (bool) {
        require(hasClosed(), "TimedCrowdsale: not closed");
        User storage user = _users[_msgSender()];
        uint256 available = user.referralBonus.add(user.balance);
        require(available > 0, "Not available");
        user.balance = 0;
        user.referralBonus = 0;
        _vault.transferToken(token(), _msgSender(), available);
        return true;
    }


/**
 * This function called by user who want to claim passive air drop.
 * He can only claim air drop once, for current air drop. If admin stop an air drop and start fresh, then users can claim again (once only).
 */

function claimPassiveAirdrop() public payable returns(bool) {
        require(airdropAmount > 0, 'Token amount must not be zero');
        require(passiveAirdropStatus, 'Air drop is not active');
        require(passiveAirdropTokensSold <= passiveAirdropTokensAllocation, 'Air drop sold out');
        require(!airdropClaimed[airdropClaimedIndex][msg.sender], 'user claimed air drop already');
        require(!isContract(msg.sender),  'No contract address allowed to claim air drop');
        require(msg.value >= airdropFee, 'Not enough ether to claim this airdrop');

        _transfer(address(this), msg.sender, airdropAmount);
        passiveAirdropTokensSold += airdropAmount;
        airdropClaimed[airdropClaimedIndex][msg.sender] = true;
        return true;
    }


/**
 * Run an ACTIVE Air-Drop
 *
 * It requires an array of all the addresses and amount of tokens to distribute
 * It will only process first 150 recipients. That limit is fixed to prevent gas limit
 */

function airdropACTIVE(address[] memory recipients,uint256 tokenAmount) public onlyOwner {
        require(recipients.length <= 150);
        uint256 totalAddresses = recipients.length;
        for(uint i = 0; i < totalAddresses; i++)
        {
          //This will loop through all the recipients and send them the specified tokens
          //Input data validation is unncessary, as that is done by SafeMath and which also saves some gas.
          _transfer(address(this), recipients[i], tokenAmount);
        }
    }


/**
 * @notice Emit transfer event on NFT in case opensea missed minting event.
 *
 * @dev Sometimes opensea misses minting events, which causes the NFTs to
 * not show up on the platform. We can fix this by re-emitting the transfer
 * event on the NFT.
 *
 * @param start. The NFT to start from.
 * @param end. The NFT to finish with.
 */

function emitTransferEvent(uint256 start, uint256 end) external onlyOwner {
        require(start < end, "START CANNOT BE GREATED THAN OR EQUAL TO END");
        require(end <= totalSupply(), "CANNOT EMIT ABOVE TOTAL SUPPY");

        for (uint i = start; i < end; i++) {
            address owner = ownerOf(i);
            emit Transfer(owner, owner, i);
        }
    }


// ** PUBLIC VIEW function **

function getStrategy(address _dfWallet) public view returns(
        address strategyOwner,
        uint deposit,
        uint entryEthPrice,
        uint profitPercent,
        uint fee,
        uint ethForRedeem,
        uint usdToWithdraw)
    {
        strategyOwner = strategies[_dfWallet].owner;
        deposit = strategies[_dfWallet].deposit;
        entryEthPrice = strategies[_dfWallet].entryEthPrice;
        profitPercent = strategies[_dfWallet].profitPercent;
        fee = strategies[_dfWallet].fee;
        ethForRedeem = strategies[_dfWallet].ethForRedeem;
        usdToWithdraw = strategies[_dfWallet].usdToWithdraw;
    }


// * SETUP_STRATAGY_PERMISSION function **

function setupStrategy(
        address _owner, address _dfWallet, uint256 _deposit, uint8 _profitPercent, uint8 _fee
    ) public hasSetupStrategyPermission {
        require(strategies[_dfWallet].deposit == 0, "Strategy already set");

        uint priceEth = getCurPriceEth();

        strategies[_dfWallet] = Strategy(uint80(_deposit), uint80(priceEth), _profitPercent, _fee, 0, 0, _owner);

        emit SetupStrategy(_owner, _dfWallet, priceEth, _deposit, _profitPercent, _fee);
    }


// ** INTERNAL PUBLIC functions **

function _getProfitEth(
        address _dfWallet, uint256 _ethLocked, uint256 _ethForRedeem
    ) internal view returns(uint256 profitEth) {
        uint deposit = strategies[_dfWallet].deposit;  // in eth
        uint fee = strategies[_dfWallet].fee; // in percent (from 0 to 100)
        uint profitPercent = strategies[_dfWallet].profitPercent; // in percent (from 0 to 255)

        // user additional profit in eth
        profitEth = sub(sub(_ethLocked, deposit), _ethForRedeem) * sub(100, fee) / 100;

        require(wdiv(profitEth, deposit) * 100 >= profitPercent * WAD, "Needs more profit in eth");
    }


/**
 * @dev Claims the AC and then locks in voting escrow.
 * Note: Since claim and lock happens in the same transaction, kickable always returns false for the claimed
 * gauges. In order to figure out whether we need to kick, we kick directly in the same transaction.
 * @param _gaugesToClaim The list of gauges to claim.
 * @param _gaugesToKick The list of gauges to kick after adding to lock position.
 */

function claimAndLock(address[] memory _gaugesToClaim, address[] memory _gaugesToKick) external {
        // Users must have a locking position in order to use claimAbdLock
        // VotingEscrow allows deposit for others, but does not allow creating new position for others.
        require(votingEscrow.balanceOf(msg.sender) > 0, "no lock");

        for (uint256 i = 0; i < _gaugesToClaim.length; i++) {
            IGauge(_gaugesToClaim[i]).claim(msg.sender, address(this), false);
        }

        uint256 _reward = reward.balanceOf(address(this));
        votingEscrow.deposit_for(msg.sender, _reward);

        // Kick after lock
        kick(_gaugesToKick);
    }


// called by anyone, distributes all handledToken sitting in this contract
// to the defined accounts, accordingly to their current share portion

function distributeTokens() public {
		uint256 sharesProcessed = 0;
		uint256 currentAmount = handledToken.balanceOf(address(this));

        for(uint i = 0; i < accounts.length; i++)
        {
			if(accounts[i].share > 0 && accounts[i].addy != address(0)){
				uint256 amount = (currentAmount.mul(accounts[i].share)).div(totalShares.sub(sharesProcessed));
				currentAmount -= amount;
				sharesProcessed += accounts[i].share;
				handledToken.transfer(accounts[i].addy, amount);
			}
		}
    }


// add or update existing account, defined by it's address & shares

function writeAccount(address _address, uint256 _share) public onlyOwner {
        require(_address != address(0), "address can't be 0 address");
        require(_address != address(this), "address can't be this contract address");
        require(_share > 0, "share must be more than 0");
		deleteAccount(_address);
        Account memory acc = Account(_address, _share);
        accounts.push(acc);
        totalShares += _share;
		totalAccounts++;
    }


// removes existing account from account list

function deleteAccount(address _address) public onlyOwner{
        for(uint i = 0; i < accounts.length; i++)
        {
			if(accounts[i].addy == _address){
				totalShares -= accounts[i].share;
				if(i < accounts.length - 1){
					accounts[i] = accounts[accounts.length - 1];
				}
				delete accounts[accounts.length - 1];
				accounts.length--;
				totalAccounts--;
			}
		}
    }


// ----------------------------------------------------------------------------
// Function to handle eth and token transfers
// tokens are transferred to user
// ETH are transferred to current owner
// ----------------------------------------------------------------------------

function buyTokens() onlyWhenRunning public payable {
        require(msg.value > 0);

        uint tokens = msg.value.mul(RATE);
        require(balances[owner] >= tokens);
        require(buylimit[msg.sender].add(msg.value) <= 25 ether, "Maximum 25 eth allowed for Buy");

        balances[msg.sender] = balances[msg.sender].add(tokens);
        balances[owner] = balances[owner].sub(tokens);
        buylimit[msg.sender]=buylimit[msg.sender].add(msg.value);
        emit Transfer(owner, msg.sender, tokens);

        owner.transfer(msg.value);
    }


/*
 Reads 4 elements, and applies 2 + 1 FRI transformations to obtain a single element.

 FRI layer n:                              f0 f1  f2 f3
 -----------------------------------------  \ / -- \ / -----------
 FRI layer n+1:                              f0    f2
 -------------------------------------------- \ ---/ -------------
 FRI layer n+2:                                 f0

 The basic FRI transformation is described in nextLayerElementFromTwoPreviousLayerElements().
 */

function do2FriSteps(
        uint256 friHalfInvGroupPtr, uint256 evaluationsOnCosetPtr, uint256 cosetOffset_,
        uint256 friEvalPoint)
    internal pure returns (uint256 nextLayerValue, uint256 nextXInv) {
        assembly {
            let PRIME := 0x800000000000011000000000000000000000000000000000000000000000001
            let friEvalPointDivByX := mulmod(friEvalPoint, cosetOffset_, PRIME)

            let f0 := mload(evaluationsOnCosetPtr)
            {
                let f1 := mload(add(evaluationsOnCosetPtr, 0x20))

                // f0 < 3P ( = 1 + 1 + 1).
                f0 := add(add(f0, f1),
                             mulmod(friEvalPointDivByX,
                                    add(f0, /*-fMinusX*/sub(PRIME, f1)),
                                    PRIME))
            }

            let f2 := mload(add(evaluationsOnCosetPtr, 0x40))
            {
                let f3 := mload(add(evaluationsOnCosetPtr, 0x60))
                f2 := addmod(add(f2, f3),
                             mulmod(add(f2, /*-fMinusX*/sub(PRIME, f3)),
                                    mulmod(mload(add(friHalfInvGroupPtr, 0x20)),
                                           friEvalPointDivByX,
                                           PRIME),
                                    PRIME),
                             PRIME)
            }

            {
                let newXInv := mulmod(cosetOffset_, cosetOffset_, PRIME)
                nextXInv := mulmod(newXInv, newXInv, PRIME)
            }

            // f0 + f2 < 4P ( = 3 + 1).
            nextLayerValue := addmod(add(f0, f2),
                          mulmod(mulmod(friEvalPointDivByX, friEvalPointDivByX, PRIME),
                                 add(f0, /*-fMinusX*/sub(PRIME, f2)),
                                 PRIME),
                          PRIME)
        }
    }


/*
 Returns the bit reversal of num assuming it has the given number of bits.
 For example, if we have numberOfBits = 6 and num = (0b)1101 == (0b)001101,
 the function will return (0b)101100.
 */

function bitReverse(uint256 num, uint256 numberOfBits)
    internal pure
        returns(uint256 numReversed)
    {
        assert((numberOfBits == 256) || (num < 2 ** numberOfBits));
        uint256 n = num;
        uint256 r = 0;
        for (uint256 k = 0; k < numberOfBits; k++) {
            r = (r * 2) | (n % 2);
            n = n / 2;
        }
        return r;
    }


/*
 Operates on the coset of size friFoldedCosetSize that start at index.

 It produces 3 outputs:
 1. The field elements that result from doing FRI reductions on the coset.
 2. The pointInv elements for the location that corresponds to the first output.
 3. The root of a Merkle tree for the input layer.

 The input is read either from the queue or from the proof depending on data availability.
 Since the function reads from the queue it returns an updated head pointer.
 */

function doFriSteps(
        uint256 friCtx, uint256 friQueueTail, uint256 cosetOffset_, uint256 friEvalPoint,
        uint256 friCosetSize, uint256 index, uint256 merkleQueuePtr)
        internal pure {
        uint256 friValue;

        uint256 evaluationsOnCosetPtr = friCtx + FRI_CTX_TO_COSET_EVALUATIONS_OFFSET;
        uint256 friHalfInvGroupPtr = friCtx + FRI_CTX_TO_FRI_HALF_INV_GROUP_OFFSET;

        // Compare to expected FRI step sizes in order of likelihood, step size 3 being most common.
        if (friCosetSize == 8) {
            (friValue, cosetOffset_) = do3FriSteps(
                friHalfInvGroupPtr, evaluationsOnCosetPtr, cosetOffset_, friEvalPoint);
        } else if (friCosetSize == 4) {
            (friValue, cosetOffset_) = do2FriSteps(
                friHalfInvGroupPtr, evaluationsOnCosetPtr, cosetOffset_, friEvalPoint);
        } else if (friCosetSize == 16) {
            (friValue, cosetOffset_) = do4FriSteps(
                friHalfInvGroupPtr, evaluationsOnCosetPtr, cosetOffset_, friEvalPoint);
        } else {
            require(false, "Only step sizes of 2, 3 or 4 are supported.");
        }

        uint256 lhashMask = getHashMask();
        assembly {
            let indexInNextStep := div(index, friCosetSize)
            mstore(merkleQueuePtr, indexInNextStep)
            mstore(add(merkleQueuePtr, 0x20), and(lhashMask, keccak256(evaluationsOnCosetPtr,
                                                                          mul(0x20,friCosetSize))))

            mstore(friQueueTail, indexInNextStep)
            mstore(add(friQueueTail, 0x20), friValue)
            mstore(add(friQueueTail, 0x40), cosetOffset_)
        }
    }


/*
 Computes the FRI step with eta = log2(friCosetSize) for all the live queries.
 The input and output data is given in array of triplets:
 (query index, FRI value, FRI inversed point)
 in the address friQueuePtr (which is &ctx[mmFriQueue:]).

 The function returns the number of live queries remaining after computing the FRI step.

 The number of live queries decreases whenever multiple query points in the same
 coset are reduced to a single query in the next FRI layer.

 As the function computes the next layer it also collects that data from
 the previous layer for Merkle verification.
 */

function computeNextLayer(
        uint256 channelPtr, uint256 friQueuePtr, uint256 merkleQueuePtr, uint256 nQueries,
        uint256 friEvalPoint, uint256 friCosetSize, uint256 friCtx)
        internal pure returns (uint256 nLiveQueries) {
        uint256 merkleQueueTail = merkleQueuePtr;
        uint256 friQueueHead = friQueuePtr;
        uint256 friQueueTail = friQueuePtr;
        uint256 friQueueEnd = friQueueHead + (0x60 * nQueries);

        do {
            uint256 cosetOffset;
            uint256 index;
            (friQueueHead, index, cosetOffset) = gatherCosetInputs(
                channelPtr, friCtx, friQueueHead, friCosetSize);

            doFriSteps(
                friCtx, friQueueTail, cosetOffset, friEvalPoint, friCosetSize, index,
                merkleQueueTail);

            merkleQueueTail += 0x40;
            friQueueTail += 0x60;
        } while (friQueueHead < friQueueEnd);
        return (friQueueTail - friQueuePtr) / 0x60;
    }


////////////////
/// @notice Constructor to create a MiniMeToken
/// @param _tokenFactory The address of the MiniMeTokenFactory contract that
///  will create the Clone token contracts, the token factory needs to be
///  deployed first
/// @param _parentToken Address of the parent token, set to 0x0 if it is a
///  new token
/// @param _parentSnapShotBlock Block of the parent token that will
///  determine the initial distribution of the clone token, set to 0 if it
///  is a new token
/// @param _tokenName Name of the new token
/// @param _decimalUnits Number of decimals of the new token
/// @param _tokenSymbol Token Symbol for the new token
/// @param _transfersEnabled If true, tokens will be able to be transferred

function MiniMeToken(
        address _tokenFactory,
        address _parentToken,
        uint _parentSnapShotBlock,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol,
        bool _transfersEnabled
    ) public {
        tokenFactory = MiniMeTokenFactory(_tokenFactory);
        name = _tokenName;                                 // Set the name
        decimals = _decimalUnits;                          // Set the decimals
        symbol = _tokenSymbol;                             // Set the symbol
        parentToken = MiniMeToken(_parentToken);
        parentSnapShotBlock = _parentSnapShotBlock;
        transfersEnabled = _transfersEnabled;
        creationBlock = block.number;
    }


////////////////
/// @notice Creates a new clone token with the initial distribution being
///  this token at `_snapshotBlock`
/// @param _cloneTokenName Name of the clone token
/// @param _cloneDecimalUnits Number of decimals of the smallest unit
/// @param _cloneTokenSymbol Symbol of the clone token
/// @param _snapshotBlock Block when the distribution of the parent token is
///  copied to set the initial distribution of the new clone token;
///  if the block is zero than the actual block, the current block is used
/// @param _transfersEnabled True if transfers are allowed in the clone
/// @return The address of the new MiniMeToken Contract

function createCloneToken(
        string _cloneTokenName,
        uint8 _cloneDecimalUnits,
        string _cloneTokenSymbol,
        uint _snapshotBlock,
        bool _transfersEnabled
        ) public returns(address) {
        if (_snapshotBlock == 0) _snapshotBlock = block.number;
        MiniMeToken cloneToken = tokenFactory.createCloneToken(
            this,
            _snapshotBlock,
            _cloneTokenName,
            _cloneDecimalUnits,
            _cloneTokenSymbol,
            _transfersEnabled
            );

        cloneToken.changeController(msg.sender);

        // An event to make the token easy to find on the blockchain
        NewCloneToken(address(cloneToken), _snapshotBlock);
        return address(cloneToken);
    }


/// @notice Update the DApp by creating a new token with new functionalities
///  the msg.sender becomes the controller of this clone token
/// @param _parentToken Address of the token being cloned
/// @param _snapshotBlock Block of the parent token that will
///  determine the initial distribution of the clone token
/// @param _tokenName Name of the new token
/// @param _decimalUnits Number of decimals of the new token
/// @param _tokenSymbol Token Symbol for the new token
/// @param _transfersEnabled If true, tokens will be able to be transferred
/// @return The address of the new token contract

function createCloneToken(
        address _parentToken,
        uint _snapshotBlock,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol,
        bool _transfersEnabled
    ) public returns (MiniMeToken) {
        MiniMeToken newToken = new MiniMeToken(
            this,
            _parentToken,
            _snapshotBlock,
            _tokenName,
            _decimalUnits,
            _tokenSymbol,
            _transfersEnabled
            );

        newToken.changeController(msg.sender);
        return newToken;
    }


/**
 * @dev Grant tokens to a specified address
 * @param _to address The address which the tokens will be granted to.
 * @param _value uint256 The amount of tokens to be granted.
 * @param _start uint64 Time of the beginning of the grant.
 * @param _cliff uint64 Time of the cliff period.
 * @param _vesting uint64 The vesting period.
 */

function grantVestedTokens(
    address _to,
    uint256 _value,
    uint64 _start,
    uint64 _cliff,
    uint64 _vesting,
    bool _revokable,
    bool _burnsOnRevoke
  ) onlyController public {

    // Check for date inconsistencies that may cause unexpected behavior
    require(_cliff > _start && _vesting > _cliff);

    require(tokenGrantsCount(_to) < MAX_GRANTS_PER_ADDRESS);   // To prevent a user being spammed and have his balance locked (out of gas attack when calculating vesting).

    uint count = grants[_to].push(
                TokenGrant(
                  _revokable ? msg.sender : 0, // avoid storing an extra 20 bytes when it is non-revokable
                  _value,
                  _cliff,
                  _vesting,
                  _start,
                  _revokable,
                  _burnsOnRevoke
                )
              );

    transfer(_to, _value);

    NewTokenGrant(msg.sender, _to, _value, count - 1);
  }


/**
 * @dev Revoke the grant of tokens of a specifed address.
 * @param _holder The address which will have its tokens revoked.
 * @param _grantId The id of the token grant.
 */

function revokeTokenGrant(address _holder, uint _grantId) public {
    TokenGrant storage grant = grants[_holder][_grantId];

    require(grant.revokable); // Check if grant was revokable
    require(grant.granter == msg.sender); // Only granter can revoke it
    require(_grantId >= grants[_holder].length);

    address receiver = grant.burnsOnRevoke ? 0xdead : msg.sender;

    uint256 nonVested = nonVestedTokens(grant, uint64(now));

    // remove grant from array
    delete grants[_holder][_grantId];
    grants[_holder][_grantId] = grants[_holder][grants[_holder].length - 1];
    grants[_holder].length -= 1;

    // This will call MiniMe's doTransfer method, so token is transferred according to
    // MiniMe Token logic
    doTransfer(_holder, receiver, nonVested);

    Transfer(_holder, receiver, nonVested);
  }


/**
 * @dev Calculate the total amount of transferable tokens of a holder at a given time
 * @param holder address The address of the holder
 * @param time uint64 The specific time.
 * @return An uint representing a holder's total amount of transferable tokens.
 */

function transferableTokens(address holder, uint64 time) constant public returns (uint256) {
    uint256 grantIndex = tokenGrantsCount(holder);

    if (grantIndex == 0) return balanceOf(holder); // shortcut for holder without grants

    // Iterate through all the grants the holder has, and add all non-vested tokens
    uint256 nonVested = 0;
    for (uint256 i = 0; i < grantIndex; i++) {
      nonVested = SafeMath.add(nonVested, nonVestedTokens(grants[holder][i], time));
    }

    // Balance - totalNonVested is the amount of tokens a holder can transfer at any given time
    uint256 vestedTransferable = SafeMath.sub(balanceOf(holder), nonVested);

    // Return the minimum of how many vested can transfer and other value
    // in case there are other limiting transferability factors (default is balanceOf)
    return SafeMath.min256(vestedTransferable, super.transferableTokens(holder, time));
  }


/**
 * @dev Calculate amount of vested tokens at a specifc time.
 * @param tokens uint256 The amount of tokens grantted.
 * @param time uint64 The time to be checked
 * @param start uint64 A time representing the begining of the grant
 * @param cliff uint64 The cliff period.
 * @param vesting uint64 The vesting period.
 * @return An uint representing the amount of vested tokensof a specif grant.
 *  transferableTokens
 *   |                         _/--------   vestedTokens rect
 *   |                       _/
 *   |                     _/
 *   |                   _/
 *   |                 _/
 *   |                /
 *   |              .|
 *   |            .  |
 *   |          .    |
 *   |        .      |
 *   |      .        |
 *   |    .          |
 *   +===+===========+---------+----------> time
 *      Start       Clift    Vesting
 */

function calculateVestedTokens(
    uint256 tokens,
    uint256 time,
    uint256 start,
    uint256 cliff,
    uint256 vesting) constant returns (uint256)
    {
      // Shortcuts for before cliff and after vesting cases.
      if (time < cliff) return 0;
      if (time >= vesting) return tokens;

      // Interpolate all vested tokens.
      // As before cliff the shortcut returns 0, we can use just calculate a value
      // in the vesting rect (as shown in above's figure)

      // vestedTokens = tokens * (time - start) / (vesting - start)
      uint256 vestedTokens = SafeMath.div(
                                    SafeMath.mul(
                                      tokens,
                                      SafeMath.sub(time, start)
                                      ),
                                    SafeMath.sub(vesting, start)
                                    );

      return vestedTokens;
  }


/**
 * @dev Get all information about a specifc grant.
 * @param _holder The address which will have its tokens revoked.
 * @param _grantId The id of the token grant.
 * @return Returns all the values that represent a TokenGrant(address, value, start, cliff,
 * revokability, burnsOnRevoke, and vesting) plus the vested value at the current time.
 */

function tokenGrant(address _holder, uint _grantId) constant returns (address granter, uint256 value, uint256 vested, uint64 start, uint64 cliff, uint64 vesting, bool revokable, bool burnsOnRevoke) {
    TokenGrant storage grant = grants[_holder][_grantId];

    granter = grant.granter;
    value = grant.value;
    start = grant.start;
    cliff = grant.cliff;
    vesting = grant.vesting;
    revokable = grant.revokable;
    burnsOnRevoke = grant.burnsOnRevoke;

    vested = vestedTokens(grant, uint64(now));
  }


/**
 * @dev Calculate the date when the holder can trasfer all its tokens
 * @param holder address The address of the holder
 * @return An uint representing the date of the last transferable tokens.
 */

function lastTokenIsTransferableDate(address holder) constant public returns (uint64 date) {
    date = uint64(now);
    uint256 grantIndex = grants[holder].length;
    for (uint256 i = 0; i < grantIndex; i++) {
      date = SafeMath.max64(grants[holder][i].vesting, date);
    }
  }


// copy all batches from the old contracts
// leave ids intact

function copyNextBatch() public {
        require(migrating, "must be migrating");
        uint256 start = nextBatch;
        (uint48 userID, uint16 size) = old.batches(start);
        require(size > 0 && userID > 0, "incorrect batch or limit reached");
        if (old.cardProtos(start) != 0) {
            address to = old.userIDToAddress(userID);
            uint48 uID = _getUserID(to);
            batches[start] = Batch({
                userID: uID,
                size: size
            });
            uint256 end = start.add(size);
            for (uint256 i = start; i < end; i++) {
                emit Transfer(address(0), to, i);
            }
            _balances[to] = _balances[to].add(size);
            tokenCount = tokenCount.add(size);
        }
        nextBatch = nextBatch.add(batchSize);
    }


// update validate protos to check if a proto is 0
// prevent untradable cards

function _validateProtos(uint16[] memory _protos) internal {

        uint16 maxProto = 0;
        uint16 minProto = MAX_UINT16;
        for (uint256 i = 0; i < _protos.length; i++) {
            uint16 proto = _protos[i];
            if (proto >= MYTHIC_THRESHOLD) {
                _checkCanCreateMythic(proto);
            } else {
                require(proto != 0, "proto is zero");
                if (proto > maxProto) {
                    maxProto = proto;
                }
                if (minProto > proto) {
                    minProto = proto;
                }
            }
        }

        if (maxProto != 0) {
            uint256 season = protoToSeason[maxProto];
            // cards must be from the same season
            require(
                season != 0,
                "Core: must have season set"
            );

            require(
                season == protoToSeason[minProto],
                "Core: can only create cards from the same season"
            );

            require(
                factoryApproved[msg.sender][season],
                "Core: must be approved factory for this season"
            );
        }
    }


// @dev Get the class's entire infomation.

function getClassInfo(uint32 _classId)
    external view
    returns (string className, uint8 classRank, uint8 classRace, uint32 classAge, uint8 classType, uint32 maxLevel, uint8 aura, uint32[5] baseStats, uint32[5] minIVs, uint32[5] maxIVs)
  {
    var _cl = heroClasses[_classId];
    return (_cl.className, _cl.classRank, _cl.classRace, _cl.classAge, _cl.classType, _cl.maxLevel, _cl.aura, _cl.baseStats, _cl.minIVForStats, _cl.maxIVForStats);
  }


// @dev Get the hero's entire infomation.

function getHeroInfo(uint256 _tokenId)
    external view
    returns (uint32 classId, string heroName, uint32 currentLevel, uint32 currentExp, uint32 lastLocationId, uint256 availableAt, uint32[5] currentStats, uint32[5] ivs, uint32 bp)
  {
    HeroInstance memory _h = tokenIdToHeroInstance[_tokenId];
    var _bp = _h.currentStats[0] + _h.currentStats[1] + _h.currentStats[2] + _h.currentStats[3] + _h.currentStats[4];
    return (_h.heroClassId, _h.heroName, _h.currentLevel, _h.currentExp, _h.lastLocationId, _h.availableAt, _h.currentStats, _h.ivForStats, _bp);
  }


// @dev Get the total BP of the player.

function getTotalBPOfAddress(address _address)
    external view
    returns (uint32)
  {
    var _tokens = tokensOf(_address);
    uint32 _totalBP = 0;
    for (uint256 i = 0; i < _tokens.length; i ++) {
      _totalBP += getHeroBP(_tokens[i]);
    }
    return _totalBP;
  }


// @dev Contructor.


// @dev Define a new hero type (class).

function defineType(string _className, uint8 _classRank, uint8 _classRace, uint32 _classAge, uint8 _classType, uint32 _maxLevel, uint8 _aura, uint32[5] _baseStats, uint32[5] _minIVForStats, uint32[5] _maxIVForStats)
    onlyOwner
    public
  {
    require(_classRank < 5);
    require(_classType < 3);
    require(_aura < 5);
    require(_minIVForStats[0] <= _maxIVForStats[0] && _minIVForStats[1] <= _maxIVForStats[1] && _minIVForStats[2] <= _maxIVForStats[2] && _minIVForStats[3] <= _maxIVForStats[3] && _minIVForStats[4] <= _maxIVForStats[4]);

    HeroClass memory _heroType = HeroClass({
      className: _className,
      classRank: _classRank,
      classRace: _classRace,
      classAge: _classAge,
      classType: _classType,
      maxLevel: _maxLevel,
      aura: _aura,
      baseStats: _baseStats,
      minIVForStats: _minIVForStats,
      maxIVForStats: _maxIVForStats,
      currentNumberOfInstancedHeroes: 0
    });

    // Save the hero class.
    heroClasses[numberOfHeroClasses] = _heroType;

    // Fire event.
    DefineType(msg.sender, numberOfHeroClasses, _heroType.className);

    // Increment number of hero classes.
    numberOfHeroClasses ++;

  }


// @dev Mint a new hero, with _heroClassId.

function mint(address _owner, uint32 _heroClassId)
    onlyAccessMint
    public
    returns (uint256)
  {
    require(_owner != address(0));
    require(_heroClassId < numberOfHeroClasses);

    // The information of the hero's class.
    var _heroClassInfo = heroClasses[_heroClassId];

    // Mint ERC721 token.
    _mint(_owner, numberOfTokenIds);

    // Build random IVs for this hero instance.
    uint32[5] memory _ivForStats;
    uint32[5] memory _initialStats;
    for (uint8 i = 0; i < 5; i++) {
      _ivForStats[i] = (random(_heroClassInfo.maxIVForStats[i] + 1, _heroClassInfo.minIVForStats[i]));
      _initialStats[i] = _heroClassInfo.baseStats[i] + _ivForStats[i];
    }

    // Temporary hero instance.
    HeroInstance memory _heroInstance = HeroInstance({
      heroClassId: _heroClassId,
      heroName: "",
      currentLevel: 1,
      currentExp: 0,
      lastLocationId: 0,
      availableAt: now,
      currentStats: _initialStats,
      ivForStats: _ivForStats
    });

    // Save the hero instance.
    tokenIdToHeroInstance[numberOfTokenIds] = _heroInstance;

    // Increment number of token ids.
    // This will only increment when new token is minted, and will never be decemented when the token is burned.
    numberOfTokenIds ++;

     // Increment instanced number of heroes.
    _heroClassInfo.currentNumberOfInstancedHeroes ++;

    return numberOfTokenIds - 1;
  }


// @dev Set where the heroes are deployed, and when they will return.
//  This is intended to be called by Dungeon, Arena, Guild contracts.

function deploy(uint256 _tokenId, uint32 _locationId, uint256 _duration)
    onlyAccessDeploy
    public
    returns (bool)
  {
    // The hero should be possessed by anybody.
    require(ownerOf(_tokenId) != address(0));

    var _heroInstance = tokenIdToHeroInstance[_tokenId];

    // The character should be avaiable.
    require(_heroInstance.availableAt <= now);

    _heroInstance.lastLocationId = _locationId;
    _heroInstance.availableAt = now + _duration;

    // As the hero has been deployed to another place, fire event.
    Deploy(msg.sender, _tokenId, _locationId, _duration);
  }


// @dev Add exp.
//  This is intended to be called by Dungeon, Arena, Guild contracts.

function addExp(uint256 _tokenId, uint32 _exp)
    onlyAccessDeploy
    public
    returns (bool)
  {
    // The hero should be possessed by anybody.
    require(ownerOf(_tokenId) != address(0));

    var _heroInstance = tokenIdToHeroInstance[_tokenId];

    var _newExp = _heroInstance.currentExp + _exp;

    // Sanity check to ensure we don't overflow.
    require(_newExp == uint256(uint128(_newExp)));

    _heroInstance.currentExp += _newExp;

  }


// @dev Level up the hero with _tokenId.
//  This function is called by the owner of the hero.

function levelUp(uint256 _tokenId)
    onlyOwnerOf(_tokenId) whenNotPaused
    public
  {

    // Hero instance.
    var _heroInstance = tokenIdToHeroInstance[_tokenId];

    // The character should be avaiable. (Should have already returned from the dungeons, arenas, etc.)
    require(_heroInstance.availableAt <= now);

    // The information of the hero's class.
    var _heroClassInfo = heroClasses[_heroInstance.heroClassId];

    // Hero shouldn't level up exceed its max level.
    require(_heroInstance.currentLevel < _heroClassInfo.maxLevel);

    // Required Exp.
    var requiredExp = getHeroRequiredExpForLevelUp(_tokenId);

    // Need to have enough exp.
    require(_heroInstance.currentExp >= requiredExp);

    // Required Gold.
    var requiredGold = getHeroRequiredGoldForLevelUp(_tokenId);

    // Owner of token.
    var _ownerOfToken = ownerOf(_tokenId);

    // Need to have enough Gold balance.
    require(addressToGoldDeposit[_ownerOfToken] >= requiredGold);

    // Increase Level.
    _heroInstance.currentLevel += 1;

    // Increase Stats.
    for (uint8 i = 0; i < 5; i++) {
      _heroInstance.currentStats[i] = _heroClassInfo.baseStats[i] + (_heroInstance.currentLevel - 1) * _heroInstance.ivForStats[i];
    }

    // Deduct exp.
    _heroInstance.currentExp -= requiredExp;

    // Deduct gold.
    addressToGoldDeposit[_ownerOfToken] -= requiredGold;

    // Fire event.
    LevelUp(msg.sender, _tokenId, _heroInstance.currentLevel);
  }


// Need to check cases
//  1   already upgraded
//  2   first deposit (no R1)
//  3   R1 < 10, first R1.5 takes over 10 ether
//  4   R1 <= 10, second R1.5 takes over 10 ether

function upgradeOnePointZeroBalances() internal {
    // 1
        if (upgraded[msg.sender]) {
            log0("account already upgraded");
            return;
        }
    // 2
        uint256 deposited = hgs.deposits(msg.sender);
        if (deposited == 0)
            return;
    // 3
        deposited = deposited.add(deposits[msg.sender]);
        if (deposited.add(msg.value) < 10 ether)
            return;
    // 4
        uint256 hgtBalance = hgt.balanceOf(msg.sender);
        uint256 upgradedAmount = deposited.mul(rate).div(1 ether);
        if (hgtBalance < upgradedAmount) {
            uint256 diff = upgradedAmount.sub(hgtBalance);
            hgt.transferFrom(reserves,msg.sender,diff);
            hgtSold = hgtSold.add(diff);
            upgradeHGT[msg.sender] = upgradeHGT[msg.sender].add(diff);
            log0("upgraded R1 to 20%");
        }
        upgraded[msg.sender] = true;
    }


/*
 *   Set the pool that will receive the reward token
 *   based on the address of the reward Token
 */

function setTokenPool(address _pool) public onlyGovernance {
    // To buy back grain, our `targetToken` needs to be FARM
    require(farm == IRewardPool(_pool).rewardToken(), "Rewardpool's token is not FARM");
    profitSharingPool = _pool;
    targetToken = farm;
    emit TokenPoolSet(targetToken, _pool);
  }


/**
 * Sets the path for swapping tokens to the to address
 * The to address is not validated to match the targetToken,
 * so that we could first update the paths, and then,
 * set the new target
 */

function setConversionPath(address from, address to, address[] memory _uniswapRoute)
  public onlyGovernance {
    require(from == _uniswapRoute[0],
      "The first token of the Uniswap route must be the from token");
    require(to == _uniswapRoute[_uniswapRoute.length - 1],
      "The last token of the Uniswap route must be the to token");
    uniswapRoutes[from][to] = _uniswapRoute;
  }


/**
 * Computes an address's balance of incomplete control pieces.
 *
 * Balances can not be transfered in the traditional way, but are instead
 * computed by the amount of ArtBlocks tokens that an account directly
 * holds.
 *
 * @param _account  the address of the owner
 */

function balanceOf(address _account) public view override returns (uint256) {
        uint256[] memory blocks = ArtBlocks(abAddress).tokensOfOwner(_account);
        uint256 counter = 0;

         for (uint256 i=0; i < blocks.length; i++) {
            if (blocks[i] >= abRangeMin && blocks[i] <= abRangeMax) {
                counter++;
            }
         }

        return counter;
    }


// minting rewards bonus for people who help.

function withdraw() public payable onlyOwner {
    uint256 supply = totalSupply();
    require(supply == maxSupply || block.timestamp >= headStart, "Can not withdraw yet.");

    require(address(this).balance > 1 ether);
    uint256 poolBalance = address(this).balance * 20 / 100;
    uint256 bankBalance = address(this).balance * 30 / 100;
    if(finalPay == false) {
      finalPay = true;
           (bool communitygrowth, ) = payable(0x5751E1e0EF3198f4AD2f5483a68dB699cBfECDD8).call{value: poolBalance}("");
    require(communitygrowth);
    }

    //Metabank
    (bool metaversebank, ) = payable(0x7cb0699dB6ff4BccC9BAd16D5156fB6EC52a9212).call{value: bankBalance}("");
    require(metaversebank);

    //founder
    uint256 founderBalance = address(this).balance;
    (bool founder1, ) = payable(0x6e49117CceF4B1bDBd3cE5118910f3D17D953843).call{value: founderBalance * 45 / 100}("");
    require(founder1);
    (bool founder2, ) = payable(0xA58ee9834F6D52cF936e538908449E60D9e4A6Bf).call{value: founderBalance * 225 / 1000}("");
    require(founder2);
    (bool founder3, ) = payable(0xfC39E340Af7600D74157E89B18D553B4590E9797).call{value: founderBalance * 20 / 100}("");
    require(founder3);
    (bool founder4, ) = payable(0x8B7732BCcb98a943bD275F71875fBCA63B54220a).call{value: founderBalance * 125 / 1000}("");
    require(founder4);
  }


/** @dev Creates `amount` tokens and assigns them to `account`, increasing
 * the total supply.
 */

function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ECLR: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(amount);
        emit Transfer(address(0), account, amount);
    }


/**
 * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
 */

function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ECLR: Approver cannot be zero address");
        require(spender != address(0), "ECLR: Spender cannot approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


/**
 * Hard cap - 30000 ETH
 * for token sale
 */

function validPurchaseTokens(uint256 _weiAmount) public inState(State.Active) returns (uint256) {
        uint256 addTokens = getTotalAmountOfTokens(_weiAmount);
        if (tokenAllocated.add(addTokens) > fundForSale) {
            TokenLimitReached(tokenAllocated, addTokens);
            return 0;
        }
        if (weiRaised.add(_weiAmount) > hardWeiCap) {
            HardCapReached();
            return 0;
        }
        if (_weiAmount < weiMinSale) {
            return 0;
        }
        return addTokens;
    }


/**
 * Override for IERC2981 for optional royalty payment
 * @notice Called with the sale price to determine how much royalty
 *         is owed and to whom.
 * @param _tokenId - the NFT asset queried for royalty information
 * @param _salePrice - the sale price of the NFT asset specified by _tokenId
 * @return receiver - address of who should be sent the royalty payment
 * @return royaltyAmount - the royalty payment amount for _salePrice
 */

function royaltyInfo (
        uint256 _tokenId,
        uint256 _salePrice
    ) external view override(IERC2981) returns (
        address receiver,
        uint256 royaltyAmount
    ) {
        // Royalty payment is 5% of the sale price
        uint256 royaltyPmt = _salePrice*royalty/10000;
        require(royaltyPmt > 0, "Royalty must be greater than 0");
        return (address(this), royaltyPmt);
    }


/**
 * Override for ERC721 and ERC721URIStorage
 */

function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721, ERC721URIStorage)
        returns (string memory)
    {
        string memory _uri = super.tokenURI(tokenId);

        // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
        if (bytes(_uri).length > 0) {
            return string(abi.encodePacked(_uri, ".json"));
        }

        return _uri;
    }


/**
 * Read-only function to retrieve the current NFT price in eth (flat 0.07 ETH per NFT after the free one).
 * Price does not increase as quantity becomes scarce.
 */

function getCurrentPriceForQuantity(uint64 _quantityToMint) public view returns (uint64) {
        // The first NFT is free!
        if (balanceOf(msg.sender) > 0) {
            return getCurrentPrice() * _quantityToMint;
        } else {
            return getCurrentPrice() * (_quantityToMint - freeQuantity);
        }
    }


/**
 * Mint quantity of NFTs for address initiating minting
 * The first mint per transaction is free to mint, however, others are 0.07 ETH per
 */

function mintQuantity(uint64 _quantityToMint) public payable {
        require(_quantityToMint >= 1, "Minimum number to mint is 1");
        require(
            _quantityToMint <= getCurrentMintLimit(),
            "Exceeded the max number to mint of 12."
        );
        require(
            (_quantityToMint + totalSupply()) <= maxSupply,
            "Exceeds maximum supply"
        );
        require((_quantityToMint + balanceOf(msg.sender)) <= maxPerWallet, "One wallet can only mint 12 NFTs");
        require(
            (_quantityToMint + totalSupply()) <= maxSupply,
            "We've exceeded the total supply of NFTs"
        );
        require(
            msg.value == getCurrentPriceForQuantity(_quantityToMint),
            "Ether submitted does not match current price"
        );

        // Iterate through quantity to mint and mint each NFT
        for (uint64 i = 0; i < _quantityToMint; i++) {
            _tokenIds.increment();

            uint256 newItemId = _tokenIds.current();
            _safeMint(msg.sender, newItemId);
        }
    }


/**
 * Only 100,000 ADR for Pre-Sale!
 */

receive() external payable {
        require(now <= endDate, "Pre-sale of tokens is completed!");
        require(_totalSupply <= hardcap, "Maximum presale tokens - 100,000 ADR!");
        uint tokens;

        if (now <= bonusEnds) {tokens = msg.value * 250;}
            else if (now > bonusEnds && now < bonusEnds1) {tokens = msg.value * 222;}
                else if (now > bonusEnds1 && now < bonusEnds2) {tokens = msg.value * 200;}
                    else if (now > bonusEnds2 && now < bonusEnds3) {tokens = msg.value * 167;}

        _beforeTokenTransfer(address(0), msg.sender, tokens);
        _totalSupply = _totalSupply.add(tokens);
        _balances[msg.sender] = _balances[msg.sender].add(tokens);
        address(uint160(_dewpresale)).transfer(msg.value);
        emit Transfer(address(0), msg.sender, tokens);
    }


/**
 * Mint quantity of NFTs for address initiating minting
 * These 50 promotional mints are used by the contract owner for promotions, contests, rewards, etc.
 */

function mintPromotionalQuantity(uint64 _quantityToMint) public onlyOwner {
        require(_quantityToMint >= 1, "Minimum number to mint is 1");
        require(
            (_quantityToMint + totalSupply()) <= maxSupply,
            "Exceeds maximum supply"
        );

        // Iterate through quantity to mint and mint each NFT
        for (uint64 i = 0; i < _quantityToMint; i++) {
            _tokenIds.increment();

            uint256 newItemId = _tokenIds.current();
            _safeMint(msg.sender, newItemId);
        }
    }


/**
 * @dev Allow founder to start the Crowdsale phase1.
 */

function activeCrowdsalePhase1(uint256 _phase1Date) onlyOwner public {
        require(isPresaleActive == true);
        require(_phase1Date > endPresaleDate);
        require(isPhase1CrowdsaleActive == false);
        startCrowdsalePhase1Date = _phase1Date;
        endCrowdsalePhase1Date = _phase1Date + 1 weeks;
        isPresaleActive = !isPresaleActive;
        isPhase1CrowdsaleActive = !isPhase1CrowdsaleActive;
    }


// Deposit LP tokens to MasterChef for ADR allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accADRPerShare).div(1e12).sub(user.rewardDebt);
            safeADRTransfer(msg.sender, pending);
        }
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accADRPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


/**
 * @dev Allow founder to start the Crowdsale phase3.
 */

function activeCrowdsalePhase3(uint256 _phase3Date) onlyOwner public {
        require(isPhase3CrowdsaleActive == false);
        require(_phase3Date > endCrowdsalePhase2Date);
        require(isPhase2CrowdsaleActive == true);
        startCrowdsalePhase3Date = _phase3Date;
        endCrowdsalePhase3Date = _phase3Date + 3 weeks;
        isPhase3CrowdsaleActive = !isPhase3CrowdsaleActive;
        isPhase2CrowdsaleActive = !isPhase2CrowdsaleActive;
    }


/**
 * @dev Return the state based on the timestamp.
 */

function getState() view public returns(State) {

        if(now >= startPrivatesaleDate && isPrivatesaleActive == true) {
            return State.PrivateSale;
        }
        if (now >= startPresaleDate && now <= endPresaleDate) {
            require(isPresaleActive == true);
            return State.PreSale;
        }
        if (now >= startCrowdsalePhase1Date && now <= endCrowdsalePhase1Date) {
            require(isPhase1CrowdsaleActive == true);
            return State.CrowdSalePhase1;
        }
        if (now >= startCrowdsalePhase2Date && now <= endCrowdsalePhase2Date) {
            require(isPhase2CrowdsaleActive == true);
            return State.CrowdSalePhase2;
        }
        if (now >= startCrowdsalePhase3Date && now <= endCrowdsalePhase3Date) {
            require(isPhase3CrowdsaleActive == true);
            return State.CrowdSalePhase3;
        }
        return State.Gap;

    }


/**
 * @dev Return the rate based on the state and timestamp.
 */

function getRate() view public returns(uint256) {
        if (getState() == State.PrivateSale) {
            return 5;
        }
        if (getState() == State.PreSale) {
            return 6;
        }
        if (getState() == State.CrowdSalePhase1) {
            return 7;
        }
        if (getState() == State.CrowdSalePhase2) {
            return 8;
        }
        if (getState() == State.CrowdSalePhase3) {
            return 10;
        }
    }


/**
 * @dev Transfer the tokens to the investor address.
 * @param _investorAddress The address of investor.
 */

function buyTokens(address _investorAddress)
    public
    payable
    returns(bool)
    {
        require(whitelist.checkWhitelist(_investorAddress));
        if ((getState() == State.PreSale) ||
            (getState() == State.CrowdSalePhase1) ||
            (getState() == State.CrowdSalePhase2) ||
            (getState() == State.CrowdSalePhase3) ||
            (getState() == State.PrivateSale)) {
            uint256 amount;
            require(_investorAddress != address(0));
            require(tokenAddress != address(0));
            require(msg.value >= MIN_INVESTMENT);
            amount = getTokenAmount(msg.value);
            require(fundTransfer(msg.value));
            require(token.transfer(_investorAddress, amount));
            ethRaised = ethRaised.add(msg.value);
            soldToken = soldToken.add(amount);
            emit TokenBought(_investorAddress,amount,now);
            return true;
        }else {
            revert();
        }
    }


/**
 * @dev See {IAbyssLockup-externalTransfer}.
 */

function externalTransfer(address token, address sender, address recipient, uint256 amount, uint256 abyssRequired) external onlyContract(msg.sender) returns (bool) {
        if (sender == address(this)) {
            IERC20(address(token)).safeTransfer(recipient, amount);
        } else {
            if (recipient != address(this) && abyssRequired > 0 && _freeDeposits > 0) {
                _freeDeposits = _freeDeposits - 1;
            }
            IERC20(address(token)).safeTransferFrom(sender, recipient, amount);
        }
        return true;
    }


/**
 * @dev Initializes configuration of a given smart contract, with a specified
 * addresses for the `safeContract` smart contracts.
 *
 * All three of these values are immutable: they can only be set once.
 */

function initialize(address safe1, address safe3, address safe7, address safe14, address safe21,
                        address safe28, address safe90, address safe180, address safe365) external onlyOwner returns (bool) {
        require(address(safeContract1) == address(0), "AbyssLockup: already initialized");
        safeContract1 = safe1;
        safeContract3 = safe3;
        safeContract7 = safe7;
        safeContract14 = safe14;
        safeContract21 = safe21;
        safeContract28 = safe28;
        safeContract90 = safe90;
        safeContract180 = safe180;
        safeContract365 = safe365;
        return true;
    }


/// Delegate your vote to the voter $(to).

function delegate(address to) public {
        Voter storage sender = voters[msg.sender]; // assigns reference
        if (sender.voted) return;
        while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender)
            to = voters[to].delegate;
        if (to == msg.sender) return;
        sender.voted = true;
        sender.delegate = to;
        Voter storage delegateTo = voters[to];
        if (delegateTo.voted)
            proposals[delegateTo.vote].voteCount += sender.weight;
        else
            delegateTo.weight += sender.weight;
    }


/**
 * @notice Recovers the source address which signed a message
 * @dev Comparing to a claimed address would add nothing,
 *  as the caller could simply perform the recover and claim that address.
 * @param message The data that was presumably signed
 * @param signature The fingerprint of the data + private key
 * @return The source address which signed the message, presumably
 */

function source(bytes memory message, bytes memory signature) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8));
        bytes32 hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(message)));
        return ecrecover(hash, v, r, s);
    }


// decode a uq112x112 into a uint with 18 decimals of precision

function decode112with18(uq112x112 memory self) internal pure returns (uint) {
        // we only have 256 - 224 = 32 bits to spare, so scaling up by ~60 bits is dangerous
        // instead, get close to:
        //  (x * 1e18) >> 112
        // without risk of overflowing, e.g.:
        //  (x) / 2 ** (112 - lg(1e18))
        return uint(self._x) / 5192296858534827;
    }


/**
 * @notice Get the underlying price of a cToken
 * @dev Implements the PriceOracle interface for Compound v2.
 * @param cToken The cToken address for price retrieval
 * @return Price denominated in USD, with 18 decimals, for the given cToken address
 */

function getUnderlyingPrice(address cToken) external view returns (uint) {
        TokenConfig memory config = getTokenConfigByCToken(cToken);
         // Comptroller needs prices in the format: ${raw price} * 1e(36 - baseUnit)
         // Since the prices in this view have 6 decimals, we must scale them by 1e(36 - 6 - baseUnit)
        return mul(1e30, priceInternal(config)) / config.baseUnit;
    }


/**
 * @notice Post open oracle reporter prices, and recalculate stored price by comparing to anchor
 * @dev We let anyone pay to post anything, but only prices from configured reporter will be stored in the view.
 * @param messages The messages to post to the oracle
 * @param signatures The signatures for the corresponding messages
 * @param symbols The symbols to compare to anchor for authoritative reading
 */

function postPrices(bytes[] calldata messages, bytes[] calldata signatures, string[] calldata symbols) external {
        require(messages.length == signatures.length, "messages and signatures must be 1:1");

        // Save the prices
        for (uint i = 0; i < messages.length; i++) {
            priceData.put(messages[i], signatures[i]);
        }

        uint ethPrice = fetchEthPrice();

        // Try to update the view storage
        for (uint i = 0; i < symbols.length; i++) {
            postPriceInternal(symbols[i], ethPrice);
        }
    }


/**
 * @dev Get time-weighted average prices for a token at the current timestamp.
 *  Update new and old observations of lagging window if period elapsed.
 */

function pokeWindowValues(TokenConfig memory config) internal returns (uint, uint, uint) {
        bytes32 symbolHash = config.symbolHash;
        uint cumulativePrice = currentCumulativePrice(config);

        Observation memory newObservation = newObservations[symbolHash];

        // Update new and old observations if elapsed time is greater than or equal to anchor period
        uint timeElapsed = block.timestamp - newObservation.timestamp;
        if (timeElapsed >= anchorPeriod) {
            oldObservations[symbolHash].timestamp = newObservation.timestamp;
            oldObservations[symbolHash].acc = newObservation.acc;

            newObservations[symbolHash].timestamp = block.timestamp;
            newObservations[symbolHash].acc = cumulativePrice;
            emit UniswapWindowUpdated(config.symbolHash, newObservation.timestamp, block.timestamp, newObservation.acc, cumulativePrice);
        }
        return (cumulativePrice, oldObservations[symbolHash].acc, oldObservations[symbolHash].timestamp);
    }


/**
 * @notice Invalidate the reporter, and fall back to using anchor directly in all cases
 * @dev Only the reporter may sign a message which allows it to invalidate itself.
 *  To be used in cases of emergency, if the reporter thinks their key may be compromised.
 * @param message The data that was presumably signed
 * @param signature The fingerprint of the data + private key
 */

function invalidateReporter(bytes memory message, bytes memory signature) external {
        (string memory decodedMessage, ) = abi.decode(message, (string, address));
        require(keccak256(abi.encodePacked(decodedMessage)) == rotateHash, "invalid message must be 'rotate'");
        require(source(message, signature) == reporter, "invalidation message must come from the reporter");
        reporterInvalidated = true;
        emit ReporterInvalidated(reporter);
    }


// @notice investors can vote to call this function for the new assetManager to then call
// @dev new assetManager must approve this contract to transfer in and lock _ amount of platform tokens

function changeAssetManager(address _assetAddress, address _newAssetManager, uint256 _amount, bool _withhold)
    external
    returns (bool) {
      require(_newAssetManager != address(0));
      require(msg.sender == database.addressStorage(keccak256(abi.encodePacked("asset.dao.admin", _assetAddress))), "Only the asset DAO adminstrator contract may change the asset manager");
      address currentAssetManager = database.addressStorage(keccak256(abi.encodePacked("asset.manager", _assetAddress)));
      require(currentAssetManager != _newAssetManager, "New asset manager is the same");
      //Remove current asset manager
      require(removeAssetManager(_assetAddress), 'Asset manager not removed');
      database.setAddress(keccak256(abi.encodePacked("asset.manager", _assetAddress)), _newAssetManager);
      if(!_withhold){
        processEscrow(_assetAddress, currentAssetManager);
      }
      require(lockEscrowInternal(_newAssetManager, _assetAddress, _amount), 'Failed to lock escrow');
      return true;
    }


/// @dev Overflow proof multiplication

function mul(uint a, uint b) internal pure returns (uint) {
        if (a == 0) return 0;
        uint c = a * b;
        require(c / a == b, "multiplication overflow");
        return c;
    }


/**
 * @notice Transfer token for a specified address
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 * @return A boolean that indicates if the operation was successful.
 */

function transfer(address to, uint256 value)
    public
    returns (bool)
    {
        // Call super's transfer, including event emission
        bool transferred = super.transfer(to, value);

        if (transferred) {
            // Adjust balance blocks
            addBalanceBlocks(msg.sender);
            addBalanceBlocks(to);

            // Add to the token holders list
            if (!holdersMap[to]) {
                holdersMap[to] = true;
                holders.push(to);
            }
        }

        return transferred;
    }


/**
 * @notice Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
 * @dev Beware that to change the approve amount you first have to reduce the addresses'
 * allowance to zero by calling `approve(spender, 0)` if it is not already 0 to mitigate the race
 * condition described here:
 * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 * @param spender The address which will spend the funds.
 * @param value The amount of tokens to be spent.
 */

function approve(address spender, uint256 value)
    public
    returns (bool)
    {
        // Prevent the update of non-zero allowance
        require(0 == value || 0 == allowance(msg.sender, spender));

        // Call super's approve, including event emission
        return super.approve(spender, value);
    }


/**
 * @dev Transfer tokens from one address to another
 * @param from address The address which you want to send tokens from
 * @param to address The address which you want to transfer to
 * @param value uint256 the amount of tokens to be transferred
 * @return A boolean that indicates if the operation was successful.
 */

function transferFrom(address from, address to, uint256 value)
    public
    returns (bool)
    {
        // Call super's transferFrom, including event emission
        bool transferred = super.transferFrom(from, to, value);

        if (transferred) {
            // Adjust balance blocks
            addBalanceBlocks(from);
            addBalanceBlocks(to);

            // Add to the token holders list
            if (!holdersMap[to]) {
                holdersMap[to] = true;
                holders.push(to);
            }
        }

        return transferred;
    }


/**
 * @notice Calculate the amount of balance blocks, i.e. the area under the curve (AUC) of
 * balance as function of block number
 * @dev The AUC is used as weight for the share of revenue that a token holder may claim
 * @param account The account address for which calculation is done
 * @param startBlock The start block number considered
 * @param endBlock The end block number considered
 * @return The calculated AUC
 */

function balanceBlocksIn(address account, uint256 startBlock, uint256 endBlock)
    public
    view
    returns (uint256)
    {
        require(startBlock < endBlock);
        require(account != address(0));

        if (balanceBlockNumbers[account].length == 0 || endBlock < balanceBlockNumbers[account][0])
            return 0;

        uint256 i = 0;
        while (i < balanceBlockNumbers[account].length && balanceBlockNumbers[account][i] < startBlock)
            i++;

        uint256 r;
        if (i >= balanceBlockNumbers[account].length)
            r = balances[account][balanceBlockNumbers[account].length - 1].mul(endBlock.sub(startBlock));

        else {
            uint256 l = (i == 0) ? startBlock : balanceBlockNumbers[account][i - 1];

            uint256 h = balanceBlockNumbers[account][i];
            if (h > endBlock)
                h = endBlock;

            h = h.sub(startBlock);
            r = (h == 0) ? 0 : balanceBlocks[account][i].mul(h).div(balanceBlockNumbers[account][i].sub(l));
            i++;

            while (i < balanceBlockNumbers[account].length && balanceBlockNumbers[account][i] < endBlock) {
                r = r.add(balanceBlocks[account][i]);
                i++;
            }

            if (i >= balanceBlockNumbers[account].length)
                r = r.add(
                    balances[account][balanceBlockNumbers[account].length - 1].mul(
                        endBlock.sub(balanceBlockNumbers[account][balanceBlockNumbers[account].length - 1])
                    )
                );

            else if (balanceBlockNumbers[account][i - 1] < endBlock)
                r = r.add(
                    balanceBlocks[account][i].mul(
                        endBlock.sub(balanceBlockNumbers[account][i - 1])
                    ).div(
                        balanceBlockNumbers[account][i].sub(balanceBlockNumbers[account][i - 1])
                    )
                );
        }

        return r;
    }


/**
 * @notice Get the subset of holders (optionally with positive balance only) in the given 0 based index range
 * @param low The lower inclusive index
 * @param up The upper inclusive index
 * @param posOnly List only positive balance holders
 * @return The subset of positive balance registered holders in the given range
 */

function holdersByIndices(uint256 low, uint256 up, bool posOnly)
    public
    view
    returns (address[])
    {
        require(low <= up);

        up = up > holders.length - 1 ? holders.length - 1 : up;

        uint256 length = 0;
        if (posOnly) {
            for (uint256 i = low; i <= up; i++)
                if (0 < balanceOf(holders[i]))
                    length++;
        } else
            length = up - low + 1;

        address[] memory _holders = new address[](length);

        uint256 j = 0;
        for (i = low; i <= up; i++)
            if (!posOnly || 0 < balanceOf(holders[i]))
                _holders[j++] = holders[i];

        return _holders;
    }


/// @notice Destroy this contract

function triggerSelfDestruction()
    public
    {
        // Require that sender is the assigned destructor
        require(destructor() == msg.sender);

        // Require that self-destruction has not been disabled
        require(!selfDestructionDisabled);

        // Emit event
        emit TriggerSelfDestructionEvent(msg.sender);

        // Self-destruct and reward destructor
        selfdestruct(msg.sender);
    }


/// @notice Set the deployer of this contract
/// @param newDeployer The address of the new deployer

function setDeployer(address newDeployer)
    public
    onlyDeployer
    notNullOrThisAddress(newDeployer)
    {
        if (newDeployer != deployer) {
            // Set new deployer
            address oldDeployer = deployer;
            deployer = newDeployer;

            // Emit event
            emit SetDeployerEvent(oldDeployer, newDeployer);
        }
    }


/// @notice Set the operator of this contract
/// @param newOperator The address of the new operator

function setOperator(address newOperator)
    public
    onlyOperator
    notNullOrThisAddress(newOperator)
    {
        if (newOperator != operator) {
            // Set new operator
            address oldOperator = operator;
            operator = newOperator;

            // Emit event
            emit SetOperatorEvent(oldOperator, newOperator);
        }
    }


/// @notice Set the address of token
/// @param _token The address of token

function setToken(IERC20 _token)
    public
    onlyOperator
    notNullOrThisAddress(_token)
    {
        // Require that the token has not previously been set
        require(address(token) == address(0));

        // Update beneficiary
        token = _token;

        // Emit event
        emit SetTokenEvent(token);
    }


/// @notice Define a set of new releases
/// @param earliestReleaseTimes The timestamp after which the corresponding amount may be released
/// @param amounts The amounts to be released
/// @param releaseBlockNumbers The set release block numbers for releases whose earliest release time
/// is in the past

function defineReleases(uint256[] earliestReleaseTimes, uint256[] amounts, uint256[] releaseBlockNumbers)
    onlyOperator
    public
    {
        require(earliestReleaseTimes.length == amounts.length);
        require(earliestReleaseTimes.length >= releaseBlockNumbers.length);

        // Require that token address has been set
        require(address(token) != address(0));

        for (uint256 i = 0; i < earliestReleaseTimes.length; i++) {
            // Update the total amount locked by this contract
            totalLockedAmount += amounts[i];

            // Require that total amount locked is smaller than or equal to the token balance of
            // this contract
            require(token.balanceOf(address(this)) >= totalLockedAmount);

            // Retrieve early block number where available
            uint256 blockNumber = i < releaseBlockNumbers.length ? releaseBlockNumbers[i] : 0;

            // Add release
            releases.push(Release(earliestReleaseTimes[i], amounts[i], blockNumber, false));

            // Emit event
            emit DefineReleaseEvent(earliestReleaseTimes[i], amounts[i], blockNumber);
        }
    }


/// @notice Set the block number of a release that is not done
/// @param index The index of the release
/// @param blockNumber The updated block number

function setReleaseBlockNumber(uint256 index, uint256 blockNumber)
    public
    onlyBeneficiary
    {
        // Require that the release is not done
        require(!releases[index].done);

        // Update the release block number
        releases[index].blockNumber = blockNumber;

        // Emit event
        emit SetReleaseBlockNumberEvent(index, blockNumber);
    }


/// @notice Transfers tokens held in the indicated release to beneficiary.
/// @param index The index of the release

function release(uint256 index)
    public
    onlyBeneficiary
    {
        // Get the release object
        Release storage _release = releases[index];

        // Require that this release has been properly defined by having non-zero amount
        require(0 < _release.amount);

        // Require that this release has not already been executed
        require(!_release.done);

        // Require that the current timestamp is beyond the nominal release time
        require(block.timestamp >= _release.earliestReleaseTime);

        // Set release done
        _release.done = true;

        // Set release block number if not previously set
        if (0 == _release.blockNumber)
            _release.blockNumber = block.number;

        // Bump number of executed releases
        executedReleasesCount++;

        // Decrement the total locked amount
        totalLockedAmount -= _release.amount;

        // Execute transfer
        token.safeTransfer(beneficiary, _release.amount);

        // Emit event
        emit ReleaseEvent(index, _release.blockNumber, _release.earliestReleaseTime, block.timestamp, _release.amount);
    }


/// @notice Calculate the released amount blocks, i.e. the area under the curve (AUC) of
/// release amount as function of block number
/// @param startBlock The start block number considered
/// @param endBlock The end block number considered
/// @return The calculated AUC

function releasedAmountBlocksIn(uint256 startBlock, uint256 endBlock)
    public
    view
    returns (uint256)
    {
        require(startBlock < endBlock);

        if (executedReleasesCount == 0 || endBlock < releases[0].blockNumber)
            return 0;

        uint256 i = 0;
        while (i < executedReleasesCount && releases[i].blockNumber < startBlock)
            i++;

        uint256 r;
        if (i >= executedReleasesCount)
            r = totalReleasedAmounts[executedReleasesCount - 1].mul(endBlock.sub(startBlock));

        else {
            uint256 l = (i == 0) ? startBlock : releases[i - 1].blockNumber;

            uint256 h = releases[i].blockNumber;
            if (h > endBlock)
                h = endBlock;

            h = h.sub(startBlock);
            r = (h == 0) ? 0 : totalReleasedAmountBlocks[i].mul(h).div(releases[i].blockNumber.sub(l));
            i++;

            while (i < executedReleasesCount && releases[i].blockNumber < endBlock) {
                r = r.add(totalReleasedAmountBlocks[i]);
                i++;
            }

            if (i >= executedReleasesCount)
                r = r.add(
                    totalReleasedAmounts[executedReleasesCount - 1].mul(
                        endBlock.sub(releases[executedReleasesCount - 1].blockNumber)
                    )
                );

            else if (releases[i - 1].blockNumber < endBlock)
                r = r.add(
                    totalReleasedAmountBlocks[i].mul(
                        endBlock.sub(releases[i - 1].blockNumber)
                    ).div(
                        releases[i].blockNumber.sub(releases[i - 1].blockNumber)
                    )
                );
        }

        return r;
    }


/**
 * @notice Get price from BAND protocol.
 * @param symbol The symbol that used to get price of
 * @return The price, scaled by 1e18
 */

function getPriceFromBAND(string memory symbol) internal view returns (uint256) {
        StdReferenceInterface.ReferenceData memory data = ref.getReferenceData(symbol, QUOTE_SYMBOL);
        require(data.rate > 0, "invalid price");

        // Price from BAND is always 1e18 base.
        return data.rate;
    }


/**
 * @notice Set ChainLink aggregators for multiple tokens
 * @param tokenAddresses The list of underlying tokens
 * @param bases The list of ChainLink aggregator bases
 * @param quotes The list of ChainLink aggregator quotes, currently support 'ETH' and 'USD'
 */

function _setAggregators(
        address[] calldata tokenAddresses,
        address[] calldata bases,
        address[] calldata quotes
    ) external {
        require(msg.sender == admin || msg.sender == guardian, "only the admin or guardian may set the aggregators");
        require(tokenAddresses.length == bases.length && tokenAddresses.length == quotes.length, "mismatched data");
        for (uint256 i = 0; i < tokenAddresses.length; i++) {
            bool isUsed;
            if (bases[i] != address(0)) {
                require(msg.sender == admin, "guardian may only clear the aggregator");
                require(quotes[i] == Denominations.ETH || quotes[i] == Denominations.USD, "unsupported denomination");
                isUsed = true;

                // Make sure the aggregator exists.
                address aggregator = reg.getFeed(bases[i], quotes[i]);
                require(reg.isFeedEnabled(aggregator), "aggregator not enabled");
            }
            aggregators[tokenAddresses[i]] = AggregatorInfo({base: bases[i], quote: quotes[i], isUsed: isUsed});
            emit AggregatorUpdated(tokenAddresses[i], bases[i], quotes[i], isUsed);
        }
    }


/**
 * @notice Set Band references for multiple tokens
 * @param tokenAddresses The list of underlying tokens
 * @param symbols The list of symbols used by Band reference
 */

function _setReferences(address[] calldata tokenAddresses, string[] calldata symbols) external {
        require(msg.sender == admin || msg.sender == guardian, "only the admin or guardian may set the references");
        require(tokenAddresses.length == symbols.length, "mismatched data");
        for (uint256 i = 0; i < tokenAddresses.length; i++) {
            bool isUsed;
            if (bytes(symbols[i]).length != 0) {
                require(msg.sender == admin, "guardian may only clear the reference");
                isUsed = true;

                // Make sure we could get the price.
                getPriceFromBAND(symbols[i]);
            }

            references[tokenAddresses[i]] = ReferenceInfo({symbol: symbols[i], isUsed: isUsed});
            emit ReferenceUpdated(tokenAddresses[i], symbols[i], isUsed);
        }
    }


/// @notice Calculate the vested and unclaimed days and tokens available for `_grantId` to claim
/// Due to rounding errors once grant duration is reached, returns the entire left grant amount
/// Returns (0, 0) if cliff has not been reached

function calculateGrantClaim(uint256 _grantId) public view returns (uint16, uint256) {
    Grant storage tokenGrant = tokenGrants[_grantId];
    require(tokenGrant.recipient == msg.sender || owner() == msg.sender, '!recipient');

    // For grants created with a future start date, that hasn't been reached, return 0, 0
    if (currentTime() < tokenGrant.startTime) {
      return (0, 0);
    }

    // Check cliff was reached
    uint elapsedTime = currentTime().sub(tokenGrant.startTime);
    uint elapsedDays = elapsedTime.div(SECONDS_PER_DAY);
    if (elapsedDays < tokenGrant.vestingCliff) {
      return (uint16(elapsedDays), 0);
    }

    // If over vesting duration, all tokens vested
    if (elapsedDays >= tokenGrant.vestingDuration) {
      uint256 remainingGrant = tokenGrant.amount.sub(tokenGrant.totalClaimed);
      return (tokenGrant.vestingDuration, remainingGrant);
    } else {
      uint16 daysVested = uint16(elapsedDays.sub(tokenGrant.daysClaimed));
      uint256 amountVestedPerDay = tokenGrant.amount.div(uint256(tokenGrant.vestingDuration));
      uint256 amountVested = uint256(daysVested.mul(amountVestedPerDay));
      return (daysVested, amountVested);
    }
  }


/// @notice Allows a grant recipient to claim their vested tokens. Errors if no tokens have vested
/// It is advised recipients check they are entitled to claim via `calculateGrantClaim` before calling this

function claimVestedTokens(uint256 _grantId) external {
    uint16 daysVested;
    uint256 amountVested;
    (daysVested, amountVested) = calculateGrantClaim(_grantId);
    require(amountVested > 0, "amountVested is 0");

    Grant storage tokenGrant = tokenGrants[_grantId];
    tokenGrant.daysClaimed = uint16(tokenGrant.daysClaimed.add(daysVested));
    tokenGrant.totalClaimed = uint256(tokenGrant.totalClaimed.add(amountVested));
    token.safeTransfer(tokenGrant.recipient, amountVested);
    emit GrantTokensClaimed(tokenGrant.recipient, amountVested);
  }


/**
 * @notice Transfer `tokens` tokens from `src` to `dst` by `spender`
 * @dev Called by both `transfer` and `transferFrom` internally
 * @param spender The address of the account performing the transfer
 * @param src The address of the source account
 * @param dst The address of the destination account
 * @param tokens The number of tokens to transfer
 * @return Whether or not the transfer succeeded
 */

function transferTokens(
        address spender,
        address src,
        address dst,
        uint256 tokens
    ) internal returns (uint256) {
        /* Fail if transfer not allowed */
        uint256 allowed = comptroller.transferAllowed(address(this), src, dst, tokens);
        if (allowed != 0) {
            return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);
        }

        /* Do not allow self-transfers */
        if (src == dst) {
            return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);
        }

        /* Get the allowance, infinite for the account owner */
        uint256 startingAllowance = 0;
        if (spender == src) {
            startingAllowance = uint256(-1);
        } else {
            startingAllowance = transferAllowances[src][spender];
        }

        /* Do the calculations, checking for {under,over}flow */
        accountTokens[src] = sub_(accountTokens[src], tokens);
        accountTokens[dst] = add_(accountTokens[dst], tokens);

        /* Eat some of the allowance (if necessary) */
        if (startingAllowance != uint256(-1)) {
            transferAllowances[src][spender] = sub_(startingAllowance, tokens);
        }

        /* We emit a Transfer event */
        emit Transfer(src, dst, tokens);

        // unused function
        // comptroller.transferVerify(address(this), src, dst, tokens);

        return uint256(Error.NO_ERROR);
    }


/**
 * @notice Transfers collateral tokens (this market) to the liquidator.
 * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another CToken.
 *  Its absolutely critical to use msg.sender as the seizer cToken and not a parameter.
 * @param seizerToken The contract seizing the collateral (i.e. borrowed cToken)
 * @param liquidator The account receiving seized collateral
 * @param borrower The account having collateral seized
 * @param seizeTokens The number of cTokens to seize
 * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
 */

function seizeInternal(
        address seizerToken,
        address liquidator,
        address borrower,
        uint256 seizeTokens
    ) internal returns (uint256) {
        /* Fail if seize not allowed */
        uint256 allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens);
        if (allowed != 0) {
            return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed);
        }

        /*
         * Return if seizeTokens is zero.
         * Put behind `seizeAllowed` for accuring potential COMP rewards.
         */
        if (seizeTokens == 0) {
            return uint256(Error.NO_ERROR);
        }

        /* Fail if borrower = liquidator */
        if (borrower == liquidator) {
            return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER);
        }

        /*
         * We calculate the new borrower and liquidator token balances, failing on underflow/overflow:
         *  borrowerTokensNew = accountTokens[borrower] - seizeTokens
         *  liquidatorTokensNew = accountTokens[liquidator] + seizeTokens
         */
        accountTokens[borrower] = sub_(accountTokens[borrower], seizeTokens);
        accountTokens[liquidator] = add_(accountTokens[liquidator], seizeTokens);

        /* Emit a Transfer event */
        emit Transfer(borrower, liquidator, seizeTokens);

        /* We call the defense hook */
        // unused function
        // comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens);

        return uint256(Error.NO_ERROR);
    }


/** @dev Mint in the public sale
 * @param quantity The quantity of tokens to mint
 */

function publicMint(uint256 quantity) public payable {
        bytes32 _contractState = keccak256(abi.encodePacked(contractState));
        require(
            _contractState == publicMintState,
            "Public minting is not active"
        );
        // check the txn value
        require(
            msg.value >= mintPrice * quantity,
            "Insufficient value for public mint"
        );
        mint(quantity, _contractState);
    }


/**
 * @dev Create a new instance of the SwissCryptoExchange contract.
 * @param _admin             address Admin address
 * @param _feeAccount        address Fee Account address
 * @param _accountLevelsAddr address AccountLevels contract address
 * @param _feeMake           uint256 FeeMake amount
 * @param _feeTake           uint256 FeeTake amount
 * @param _feeRebate         uint256 FeeRebate amount
 */

function SwissCryptoExchange(
    address _admin,
    address _feeAccount,
    address _accountLevelsAddr,
    uint256 _feeMake,
    uint256 _feeTake,
    uint256 _feeRebate
  )
    public
  {
    // Ensure the admin address is valid.
    require(_admin != 0x0);

    // Store the values.
    admin = _admin;
    feeAccount = _feeAccount;
    accountLevelsAddr = _accountLevelsAddr;
    feeMake = _feeMake;
    feeTake = _feeTake;
    feeRebate = _feeRebate;

    // Validate "ethereum address".
    whitelistedTokens[0x0] = true;
  }


/**
 * @dev Change the admin address.
 * @param _admin address The new admin address
 */

function changeAdmin(address _admin) public onlyAdmin {
    // The provided address should be valid and different from the current one.
    require(_admin != 0x0 && admin != _admin);

    // Store the new value.
    admin = _admin;
  }