//------------------------------------------------------
// return game info
//------------------------------------------------------

function getGameInfo(uint _hGame)  constant  returns (EndReason _reason, uint _players, uint _payout, bool _active, address _winner )
	{
		gameInstance ngame = games[_hGame];
		_active = ngame.active;
		_players = ngame.numPlayers;
		_winner = ngame.winner;
		_payout = ngame.payout;
		_reason = ngame.reasonEnded;

	}


//------------------------------------------------------
// get operation gas amounts
//------------------------------------------------------

function getOpGas() constant returns (uint _ra, uint _sg, uint _wp, uint _rf, uint _fg)
	{
		_ra = raGas; // register arb
		_sg = sgGas; // start game
		_wp = wpGas; // winner paid
		_rf = rfGas; // refund
		_fg = feeGas; // rake fee gas
	}


//------------------------------------------------------
// set operation gas amounts for forwading operations
//------------------------------------------------------

function setOpGas(uint _ra, uint _sg, uint _wp, uint _rf, uint _fg)
	{
		if (msg.sender != owner)
			throw;

		raGas = _ra;
		sgGas = _sg;
		wpGas = _wp;
		rfGas = _rf;
		feeGas = _fg;
	}


//------------------------------------------------------
// flush the house fees whenever commanded to.
// ignore the threshold and the last payout time
// but this time only reset lastpayouttime upon success
//------------------------------------------------------

function flushHouseFees()
	{
		if (msg.sender != owner) {
			StatEvent("only owner calls this function");
		} else if (houseFeeHoldover > 0) {
			uint ntmpho = houseFeeHoldover;
			houseFeeHoldover = 0;
			if (!tokenPartner.call.gas(feeGas).value(ntmpho)()) {
				houseFeeHoldover = ntmpho; // put it back
				StatEvent("House-Fee Error2");
			} else {
				lastPayoutTime = now;
 				StatEvent("House-Fee Paid");
	 		}
		}

	}


//------------------------------------------------------
// set the token partner
//------------------------------------------------------

function setTokenPartner(address _addr) public
	{
		if (msg.sender != owner) {
			throw;
		}

		if ((settingsState == SettingStateValue.lockedRelease)
			&& (tokenPartner == address(0))) {
			tokenPartner = _addr;
			StatEvent("Token Partner Final!");
		} else if (settingsState != SettingStateValue.lockedRelease) {
			tokenPartner = _addr;
			StatEvent("Token Partner Assigned!");
		}

	}


/**
 * @dev public function that is used to determine the current rate for token / ETH conversion
 * @dev there exists a case where rate cant be set to 0, which is fine.
 * @return The current token rate
 */

function getRate() public view returns(uint256) {
        require( priceOfEMLTokenInUSDPenny !=0 );
        require( priceOfEthInUSD !=0 );
        uint256 rate;

        if(overridenBonusValue > 0){
            rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(overridenBonusValue.add(100)).div(100);
        } else {
            if (now <= (startTime + 1 days)) {
                rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(bonusPercent1.add(100)).div(100);
            } if (now > (startTime + 1 days) && now <= (startTime + 2 days)) {
                rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(bonusPercent2.add(100)).div(100);
            } else {
                rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(bonusPercent3.add(100)).div(100);
            }
        }
        return rate;
    }


/** @dev Function for buying EML tokens using ether
 * @param _investorAddr The address that should receive bought tokens
 */

function buyTokensUsingEther(address _investorAddr) internal whenNotPaused {
        require(_investorAddr != address(0));
        require(validPurchase());

        uint256 weiAmount = msg.value;
        uint256 returnToSender = 0;

        // final rate after including rate value and bonus amount.
        uint256 finalConversionRate = getRate();

        // Calculate EML token amount to be transferred
        uint256 tokens = weiAmount.mul(finalConversionRate);

        // Distribute only the remaining tokens if final contribution exceeds hard cap
        if (totalTokensSoldandAllocated.add(tokens) > hardCap) {
            tokens = hardCap.sub(totalTokensSoldandAllocated);
            weiAmount = tokens.div(finalConversionRate);
            returnToSender = msg.value.sub(weiAmount);
        }

        // update state and balances
        etherInvestments[_investorAddr] = etherInvestments[_investorAddr].add(weiAmount);
        tokensSoldForEther[_investorAddr] = tokensSoldForEther[_investorAddr].add(tokens);
        totalTokensSoldByEtherInvestments = totalTokensSoldByEtherInvestments.add(tokens);
        totalEtherRaisedByCrowdsale = totalEtherRaisedByCrowdsale.add(weiAmount);
        totalTokensSoldandAllocated = totalTokensSoldandAllocated.add(tokens);

        // assert implies it should never fail
        assert(token.transferFrom(owner, _investorAddr, tokens));
        emit TokenPurchasedUsingEther(_investorAddr, weiAmount, tokens);

        // Forward funds
        multisigWallet.transfer(weiAmount);

        // Update token contract.
        _postValidationUpdateTokenContract();

        // Return funds that are over hard cap
        if (returnToSender > 0) {
            msg.sender.transfer(returnToSender);
        }
    }


/** @dev Internal function that is used to check if the incoming purchase should be accepted.
 * @return True if the transaction can buy tokens
 */

function validPurchase() internal view returns(bool) {
        bool withinPeriod = now >= startTime && now <= endTime;
        bool minimumPurchase = msg.value >= 1*(10**18);
        bool hardCapNotReached = totalTokensSoldandAllocated < hardCap;
        return withinPeriod && hardCapNotReached && minimumPurchase;
    }


/** @dev Returns ether to token holders in case soft cap is not reached.
 */

function claimRefund() public whenNotPaused onlyOwner {
        require(now>endTime);
        require(totalTokensSoldandAllocated<soft_cap);
        uint256 amount = etherInvestments[msg.sender];

        if (address(this).balance >= amount) {
            etherInvestments[msg.sender] = 0;
            if (amount > 0) {
                msg.sender.transfer(amount);
                emit Refund(msg.sender, amount);
            }
        }
      }


/** @dev Allocates EML tokens to an investor address called automatically
 * after receiving fiat or btc investments from KYC whitelisted investors.
 * @param beneficiary The address of the investor
 * @param tokenCount The number of tokens to be allocated to this address
 */

function allocateTokens(address beneficiary, uint256 tokenCount) public onlyOwner returns(bool success) {
        require(beneficiary != address(0));
        require(validAllocation(tokenCount));

        uint256 tokens = tokenCount;

        /* Allocate only the remaining tokens if final contribution exceeds hard cap */
        if (totalTokensSoldandAllocated.add(tokens) > hardCap) {
            tokens = hardCap.sub(totalTokensSoldandAllocated);
        }

        /* Update state and balances */
        allocatedTokens[beneficiary] = allocatedTokens[beneficiary].add(tokens);
        totalTokensSoldandAllocated = totalTokensSoldandAllocated.add(tokens);
        totalTokensAllocated = totalTokensAllocated.add(tokens);

        // assert implies it should never fail
        assert(token.transferFrom(owner, beneficiary, tokens));
        emit TokensAllocated(beneficiary, tokens);

        /* Update token contract. */
        _postValidationUpdateTokenContract();
        return true;
    }


/** @dev public function that is used to determine the current rate for ETH to EML conversion
 * @return The current token rate
 */

function getRate() public view returns(uint256) {
        require(priceOfEMLTokenInUSDPenny > 0 );
        require(priceOfEthInUSD > 0 );
        uint256 rate;

        if(overridenBonusValue > 0){
            rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(overridenBonusValue.add(100)).div(100);
        } else {
            rate = priceOfEthInUSD.mul(100).div(priceOfEMLTokenInUSDPenny).mul(bonusPercent1.add(100)).div(100);
        }
        return rate;
    }


/*
 * Declaring the customized details of the token. The token will be called Tratok, with a total supply of 100 billion tokens.
 * It will feature five decimal places and have the symbol TRAT.
 */

function Tratok() {

        //we will create 100 Billion Coins and send them to the creating wallet.
        balances[msg.sender] = 10000000000000000;
        totalSupply = 10000000000000000;
        name = "Tratok";
        decimals = 5;
        symbol = "TRAT";
    }


/*
 *Approve and enact the contract.
 *
 */

function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns(bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);

        //If the call fails, result to "vanilla" approval.
        if (!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) {
            throw;
        }
        return true;
    }


//Checks staked amount

function depositsOf(address account)
        external
        view
        returns (uint256[] memory)
    {
        EnumerableSet.UintSet storage depositSet = _deposits[account];
        uint256[] memory tokenIds = new uint256[](depositSet.length());

        for (uint256 i; i < depositSet.length(); i++) {
            tokenIds[i] = depositSet.at(i);
        }

        return tokenIds;
    }


//Calculate rewards amount by address/tokenIds[]

function calculateRewards(address account, uint256[] memory tokenIds)
        public
        view
        returns (uint256[] memory rewards)
    {
        rewards = new uint256[](tokenIds.length);

        for (uint256 i; i < tokenIds.length; i++) {
            uint256 tokenId = tokenIds[i];
            rewards[i] =
                rate *
                (_deposits[account].contains(tokenId) ? 1 : 0) *
                (block.number - _depositBlocks[account][tokenId]);
        }

        return rewards;
    }


//Staking function

function stake(uint256[] calldata tokenIds) external whenNotPaused {
        require(msg.sender != nftAddress, "Invalid address");
        require(
            nft.isApprovedForAll(msg.sender, address(this)),
            "This contract is not approved to transfer your NFT."
        );
        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                nft.ownerOf(tokenIds[i]) == msg.sender,
                "You do not own this NFT."
            );
        }

        claimRewards(tokenIds);
        for (uint256 i; i < tokenIds.length; i++) {
            IERC721(nftAddress).safeTransferFrom(
                msg.sender,
                address(this),
                tokenIds[i],
                ""
            );
            _deposits[msg.sender].add(tokenIds[i]);
        }
    }


// end Uniswap stuff

function rebase(uint256 epoch, int256 supplyDelta)
        external
        returns (uint256)
    {
        require(msg.sender == info.chef);
        if (supplyDelta == 0) {
             LogRebase(epoch, info.totalFrozen);
            return info.totalFrozen;
        }

        if (supplyDelta < 0) {
            info.totalFrozen = info.totalFrozen.sub(uint256(supplyDelta));
        }

         LogRebase(epoch, info.totalFrozen);
        return info.totalFrozen;
    }


//==================================================================================//
// Unstaking functions
// Unstake % for self

function unstake(uint basisPoints, address pool) public returns (bool success) {
        require((basisPoints > 0 && basisPoints <= 10000), "Must be valid BasisPoints");
        uint _units = math.calcPart(basisPoints, memberData[msg.sender].stakeData[pool].stakeUnits);
        unstakeExact(_units, pool);
        return true;
    }


// Unstake an exact qty of units

function unstakeExact(uint units, address pool) public returns (bool success) {
        require(units <= memberData[msg.sender].stakeData[pool].stakeUnits, "Must own the units");
        uint _outputVether = math.calcShare(units, poolData[pool].poolUnits, poolData[pool].vether);
        uint _outputAsset = math.calcShare(units, poolData[pool].poolUnits, poolData[pool].asset);
        _handleUnstake(units, _outputVether, _outputAsset, msg.sender, pool);
        return true;
    }


// Unstake Exact Asymmetrically

function unstakeExactAsymmetric(uint units, address pool, bool toVether) public returns (uint outputAmount){
        require(units <= memberData[msg.sender].stakeData[pool].stakeUnits, "Must own the units");
        uint _poolUnits = poolData[pool].poolUnits;
        require(units < _poolUnits, "Must not be last staker");
        uint _outputVether; uint _outputAsset;
        if(toVether){
            _outputVether = math.calcAsymmetricShare(units, _poolUnits, poolData[pool].vether);
            _outputAsset = 0;
            outputAmount = _outputVether;
        } else {
            _outputVether = 0;
            _outputAsset = math.calcAsymmetricShare(units, _poolUnits, poolData[pool].asset);
            outputAmount = _outputAsset;
        }
        _handleUnstake(units, _outputVether, _outputAsset, msg.sender, pool);
        return outputAmount;
    }


// Internal - handle Unstake

function _handleUnstake(uint _units, uint _outputVether, uint _outputAsset, address payable _member, address _pool) internal {
        _decrementPoolBalances(_units, _outputVether, _outputAsset, _pool);
        _removeDataForMember(_member, _units, _pool);
        emit Unstaked(_pool, _member, _outputAsset, _outputVether, _units);
        _handleTransferOut(_pool, _outputAsset, _member);
        _handleTransferOut(VETHER, _outputVether, _member);
    }


//==================================================================================//
// Upgrade functions
// Upgrade from this contract to a new one - opt in

function upgrade(address payable newContract, address pool) public {
        uint _units = memberData[msg.sender].stakeData[pool].stakeUnits;
        uint _outputVether = math.calcShare(_units, poolData[pool].poolUnits, poolData[pool].vether);
        uint _outputAsset = math.calcShare(_units, poolData[pool].poolUnits, poolData[pool].asset);
        _decrementPoolBalances(_units, _outputVether, _outputAsset, pool);
        _removeDataForMember(msg.sender, _units, pool);
        emit Unstaked(pool, msg.sender, _outputAsset, _outputVether, _units);
        ERC20(VETHER).approve(newContract, _outputVether);
        if(pool == address(0)){
            POOLS(newContract).stakeForMember{value:_outputAsset}(_outputVether, _outputAsset, pool, msg.sender);
        } else {
            ERC20(pool).approve(newContract, _outputAsset);
            POOLS(newContract).stakeForMember(_outputVether, _outputAsset, pool, msg.sender);
        }
    }


//==================================================================================//
// Swapping functions

function swap(uint inputAmount, address withAsset, address toAsset) public payable returns (uint outputAmount, uint fee) {
        require(now < poolData[address(0)].genesis + DAYCAP, "Must not be after Day Cap");
        require(withAsset != toAsset, "Asset must not be the same");
        uint _actualAmount = _handleTransferIn(withAsset, inputAmount); uint _transferAmount;
        if(withAsset == VETHER){
            (outputAmount, fee) = _swapVetherToAsset(_actualAmount, toAsset);
            emit Swapped(VETHER, toAsset, _actualAmount, 0, outputAmount, fee, msg.sender);
        } else if(toAsset == VETHER) {
            (outputAmount, fee) = _swapAssetToVether(_actualAmount, withAsset);
            emit Swapped(withAsset, VETHER, _actualAmount, 0, outputAmount, fee, msg.sender);
        } else {
            (_transferAmount, outputAmount, fee) = _swapAssetToAsset(_actualAmount, withAsset, toAsset);
            emit Swapped(withAsset, toAsset, _actualAmount, _transferAmount, outputAmount, fee, msg.sender);
        }
        _handleTransferOut(toAsset, outputAmount, msg.sender);
        return (outputAmount, fee);
    }


//==================================================================================//
// Asset Transfer Functions

function _handleTransferIn(address _asset, uint _amount) internal returns(uint actual){
        if(_amount > 0) {
            if(_asset == address(0)){
                require((_amount == msg.value), "Must get Eth");
                actual = _amount;
            } else {
                uint startBal = ERC20(_asset).balanceOf(address(this));
                ERC20(_asset).transferFrom(msg.sender, address(this), _amount);
                actual = ERC20(_asset).balanceOf(address(this)).sub(startBal);
            }
        }
    }


/// @title Dequeus a Play
/// @notice Removes the oldest play from the queue.
/// @dev reverts if an attempt is made to deueue when the queue is empty.
/// Only the owner may call this method.
/// @return player address fo the player
/// @return amount the original value of hte player's bet.

function dequeue() public returns (address payable player, uint amount) {
        require(msg.sender == owner, 'Access Denied');
        require(!isEmpty(),'Queue is empty');
        (player,amount) = (queue[first].player,queue[first].amount);
        delete queue[first];
        first += 1;
        if(last < first) {
            first = 1;
            last = 0;
        }
    }


/// @title Total value of bets from players in queue.
/// @notice Enumerates the players in the queu and returns the
/// sum of the value of all the bets associated with the players.
/// @dev only the owner may call this method.
/// @return total the total value of the bets for the players contained
/// within this queue.

function totalAmount() public view returns (uint total)
    {
        require(msg.sender == owner, 'Access Denied');
        total = 0;
        for(uint i = first; i <= last; i ++ ) {
            total = total + queue[i].amount;
        }
    }


/// @title Ends the Game
/// @notice Ends the game, returning any unresolved plays to their
/// originating addresses, if possible.  If transfers fail, a
/// `RefundFailure` event will be raised and it will be up to the
/// owner of the contract to manually resolve any issues.
/// @dev Only the owner of the contract may call this method.

function end() external
    {
        require(owner == msg.sender, 'Access Denied');
        require(open, 'Game is already finished.');
        open = false;
        openingMove = Move.None;
        while(!openingMovers.isEmpty())
        {
            (address payable player, uint bet) = openingMovers.dequeue();
            if(!player.send(bet))
            {
                emit RefundFailure(player,bet);
            }
        }
        emit GameClosed();
    }


/*!	Transfer tokens to multipe destination addresses
 		Returns list with appropriate (by index) successful statuses.
 		(string with 0 or 1 chars)
 	 */

function transferMulti(address [] _tos, uint256 [] _values) public onlyMigrationGate returns (string) {
		require(_tos.length == _values.length);
		bytes memory return_values = new bytes(_tos.length);

		for (uint256 i = 0; i < _tos.length; i++) {
			address _to = _tos[i];
			uint256 _value = _values[i];
			return_values[i] = byte(48); //'0'

			if (_to != address(0) &&
				_value <= balances[msg.sender]) {

				bool ok = transfer(_to, _value);
				if (ok) {
					return_values[i] = byte(49); //'1'
				}
			}
		}
		return string(return_values);
	}


/*!	Migrate tokens to the new token contract
 		The method can be only called when migration agent is set.

 		Can be called by user(holder) that would like to transfer
 		coins to new contract immediately.
 	 */

function migrate() public returns (bool) {
		require(migrationAgent != 0x0);
		uint256 value = balances[msg.sender];
		balances[msg.sender] = balances[msg.sender].sub(value);
		totalSupply_ = totalSupply_.sub(value);
		MigrationAgent(migrationAgent).migrateFrom(msg.sender, value);
		// Notify anyone listening that this migration took place
		Migrate(msg.sender, value);
		return true;
	}


/*!	Migrate holders of tokens to the new contract
 		The method can be only called when migration agent is set.

 		Can be called only by owner (onlyOwner)
 	 */

function migrateHolders(uint256 count) public onlyOwner returns (bool) {
		require(count > 0);
		require(migrationAgent != 0x0);
		// Calculate bounds for processing
		count = migrationCountComplete.add(count);
		if (count > holders.length) {
			count = holders.length;
		}
		// Process migration
		for (uint256 i = migrationCountComplete; i < count; i++) {
			address holder = holders[i];
			uint value = balances[holder];
			balances[holder] = balances[holder].sub(value);
			totalSupply_ = totalSupply_.sub(value);
			MigrationAgent(migrationAgent).migrateFrom(holder, value);
			// Notify anyone listening that this migration took place
			Migrate(holder, value);
		}
		migrationCountComplete = count;
		return true;
	}


/*
 * @notify Modify escrowPaused
 * @param parameter Must be "escrowPaused"
 * @param data The new value for escrowPaused
 */

function modifyParameters(bytes32 parameter, uint256 data) external isAuthorized {
        if (parameter == "minStakedTokensToKeep") {
            require(data <= maxStakedTokensToKeep, "MinimalLenderFirstResortOverlay/minStakedTokensToKeep-over-limit");
            staking.modifyParameters(parameter, data);
        } else if (
            parameter == "escrowPaused"    ||
            parameter == "bypassAuctions"  ||
            parameter == "tokensToAuction" ||
            parameter == "systemCoinsToRequest"
            ) staking.modifyParameters(parameter, data);
        else revert("MinimalLenderFirstResortOverlay/modify-forbidden-param");
    }


/*
 * The mint function
 */

function summonEggZFromSpace(uint256 num, uint256 price) internal {
        uint256 supply = totalSupply();
        require( canMint,                              "Sale paused" );
        require( supply + num <= 10000 - _reserved,      "No EggZ left :'(" );
        require( msg.value >= price * num,             "Not enough ether sent" );
        require( earlySupportersGiveawayDone,           "You can't summon EggZ till early supporters and team haven't had their EggZ");

        for(uint256 i; i < num; i++){
            _safeMint( msg.sender, supply + i );
        }
    }


/*
 *   Used to airdrop 1 egg to each _addrs
 *   I'm not using airdropToWallet in this function to avoid
 *   calling require _addrs.length amounts of time
 *
 */

function airdropOneEggToWallets(address[] memory _addrs) external onlyOwner() {
        uint256 supply = totalSupply();
        uint256 amount = _addrs.length;
        require( earlySupportersGiveawayDone, "You can't summon EggZ till early supporters and team haven't had their EggZ");
        require( supply + amount <= _reserved, "Not enough reserved EggZ");

        for(uint256 i; i < amount; i++) {
            _safeMint( _addrs[i], supply + i);
        }

        _reserved -= amount;
    }


/*
 *   Used to airdrop amount egg to addr
 *
 */

function airdropToWallet(address addr, uint256 amount) external onlyOwner() {
        uint256 supply = totalSupply();
        require( earlySupportersGiveawayDone, "You can't summon EggZ till early supporters and team haven't had their EggZ");
        require( supply + amount <= _reserved, "Not enough reserved EggZ");

        for(uint256 i; i < amount; i++) {
            _safeMint( addr, supply + i);
        }

        _reserved -= amount;
    }


/*
 *   Used to airdrop the 50 EggZ with early supporter custom trait
 *   Can only be called once
 */

function earlySupportersGiveaway(address[] memory _addrs) external onlyOwner() {
        uint256 supply = totalSupply();
        require( !earlySupportersGiveawayDone, "Early supporters giveaway has already been done !");
        require( _addrs.length == 50,   "There should be 50 adresses to run the early supporters giveaway");

        for(uint256 i; i < _addrs.length; i++) {
            _safeMint( _addrs[i], supply + i );
        }

        earlySupportersGiveawayDone = true;
        _reserved -= 50;

    }


/*************************************************
 *
 *      UTILITY PART
 *
 * ***********************************************/

function listEggZOfOwner(address _owner) public view returns(uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);

        uint256[] memory tokensId = new uint256[](tokenCount);
        for(uint256 i; i < tokenCount; i++){
            tokensId[i] = tokenOfOwnerByIndex(_owner, i);
        }
        return tokensId;
    }


/// For creating Waifus

function _createWaifu(string _name, address _owner, uint256 _price) private {
    Waifu memory _waifu = Waifu({
      name: _name
    });
    uint256 newWaifuId = waifus.push(_waifu) - 1;

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

    Birth(newWaifuId, _name, _owner);

    waifuIndexToPrice[newWaifuId] = _price;

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


// add externalRandomNumber to prevent node validators exploiting

function drawing(uint256 _externalRandomNumber) external onlyAdmin {
        require(!drawed(), "reset?");
        require(drawingPhase, "enter drawing phase first");
        bytes32 _structHash;
        uint256 _randomNumber;
        uint8 _maxNumber = maxNumber;
        bytes32 _blockhash = blockhash(block.number-1);

        // waste some gas fee here
        for (uint i = 0; i < 5; i++) {
            getTotalRewards(issueIndex);
        }
        uint256 gasremaining = gasleft();

        // 1
        _structHash = keccak256(
            abi.encode(
                _blockhash,
                totalAddresses,
                gasremaining,
                _externalRandomNumber
            )
        );
        _randomNumber  = uint256(_structHash);
        assembly {_randomNumber := add(mod(_randomNumber, _maxNumber),1)}
        winningNumbers[0]=uint8(_randomNumber);

        // 2
        _structHash = keccak256(
            abi.encode(
                _blockhash,
                totalAmount,
                gasremaining,
                _externalRandomNumber
            )
        );
        _randomNumber  = uint256(_structHash);
        assembly {_randomNumber := add(mod(_randomNumber, _maxNumber),1)}
        winningNumbers[1]=uint8(_randomNumber);

        // 3
        _structHash = keccak256(
            abi.encode(
                _blockhash,
                lastTimestamp,
                gasremaining,
                _externalRandomNumber
            )
        );
        _randomNumber  = uint256(_structHash);
        assembly {_randomNumber := add(mod(_randomNumber, _maxNumber),1)}
        winningNumbers[2]=uint8(_randomNumber);

        // 4
        _structHash = keccak256(
            abi.encode(
                _blockhash,
                gasremaining,
                _externalRandomNumber
            )
        );
        _randomNumber  = uint256(_structHash);
        assembly {_randomNumber := add(mod(_randomNumber, _maxNumber),1)}
        winningNumbers[3]=uint8(_randomNumber);
        historyNumbers[issueIndex] = winningNumbers;
        historyAmount[issueIndex] = calculateMatchingRewardAmount();
        drawingPhase = false;
        emit Drawing(issueIndex, winningNumbers);
    }


// Set the allocation for one reward

function setAllocation(uint8 _allcation1, uint8 _allcation2, uint8 _allcation3, uint8 _allcation4) external onlyAdmin {
        require (_allcation1 + _allcation2 + _allcation3 + _allcation4 < 95, 'exceed 95');
        allocation = [_allcation1, _allcation2, _allcation3, _allcation4];
    }


// Only method of the contract

function add() public payable{
        if (msg.value == 500 finney) { // To participate you must pay 500 finney (0.5 ETH)
            players[num_players] = msg.sender; //save address of player

            num_players++; // One player is added, so we increase the player counter

            // Transfer the just now added 0.5 ETH to player position num_players divided by 2.
            // This payout is done 2 times for one player, because odd and even number divided by 2 is the same integer. = 1 ETH return
            players[num_players/2].transfer(address(this).balance);
        }
        else
            revert(); // Error executing the function
    }


// finalization function called by the finalize function that will distribute
// the remaining tokens

function finalization() internal {
    uint256 tokensSold = token.totalSupply();
    uint256 finalTotalSupply = cap.mul(rate).mul(4);

    // send the 10% of the final total supply to the founders
    uint256 foundersTokens = finalTotalSupply.div(10);
    token.mint(foundersAddress, foundersTokens);

    // send the 65% plus the unsold tokens in ICO to the foundation
    uint256 foundationTokens = finalTotalSupply.sub(tokensSold)
      .sub(foundersTokens);
    token.mint(foundationAddress, foundationTokens);

    super.finalization();
  }


/**
 * Token Burn.
 */

function burn(uint256 _value) public onlyOwner returns (bool success) {
        require(_value <= balances[msg.sender]);
        address burner = msg.sender;
        balances[burner] = balances[burner].sub(_value);
        totalSupply_ = totalSupply_.sub(_value);
        emit Burn(burner, _value);
        emit Transfer(burner, address(0), _value);
        return true;
    }


// @dev mint a single token to each address passed in through calldata
// @param _addresses Array of addresses to send a single token to

function mintReserveToAddresses(address[] calldata _addresses) external  onlyOwner {
      uint256 _quantity = _addresses.length;
      require(_quantity + _owners.length <= GENESIS_ODDIES_MAX_SUPPLY,"Purchase exceeds available supply.");
      for (uint256 i = 0; i < _quantity; i++) {
          _safeMint(_addresses[i]);
      }
  }


/**
 * @notice deposit backing tokens to be locked, and generate wrapped tokens to recipient
 * @param recipient         address to receive wrapped tokens
 * @param amount            amount of tokens to wrap
 * @return true if successful
 */

function wrapTo(address recipient, uint256 amount) public returns(bool) {
        require(recipient != address(0), "Recipient cannot be zero address");

        // transfer backing token from sender to this contract to be locked
        _backingToken.transferFrom(msg.sender, address(this), amount);

        // update how many tokens the sender has locked in total
        _lockedBalances[msg.sender] = _lockedBalances[msg.sender].add(amount);

        // mint wTokens to recipient
        _mint(recipient, amount);

        emit Mint(recipient, amount);
        return true;
    }


/**
 * @notice burn wrapped tokens to unlock backing tokens to recipient
 * @param recipient   address to receive backing tokens
 * @param amount      amount of tokens to unlock
 * @return true if successful
 */

function unwrapTo(address recipient, uint256 amount) public returns (bool) {
        require(recipient != address(0), "Recipient cannot be zero address");

        // burn wTokens from sender, burn should revert if not enough balance
        _burn(msg.sender, amount);

        // update how many tokens the sender has locked in total
        _lockedBalances[msg.sender] = _lockedBalances[msg.sender].sub(amount, "Cannot unlock more than the locked amount");

        // transfer backing token from this contract to recipient
        _backingToken.transfer(recipient, amount);

        emit Burn(msg.sender, amount);
        return true;
    }


/**
 * @notice withdraw all accrued dividends by the sender to the recipient
 * @param recipient     address to receive dividends
 * @return true if successful
 */

function claimAllDividendsTo(address recipient) public returns (bool) {
        require(recipient != address(0), "Recipient cannot be zero address");

        consolidateDividends(msg.sender);

        uint256 dividends = _dividends[msg.sender].consolidatedAmount;

        _dividends[msg.sender].consolidatedAmount = 0;

        _dai.transfer(recipient, dividends);

        emit DividendClaimed(msg.sender, dividends);
        return true;
    }


/**
 * @notice withdraw portion of dividends by the sender to the recipient
 * @param recipient     address to receive dividends
 * @param amount        amount of dividends to withdraw
 * @return true if successful
 */

function claimDividendsTo(address recipient, uint256 amount) public returns (bool) {
        require(recipient != address(0), "Recipient cannot be zero address");

        consolidateDividends(msg.sender);

        uint256 dividends = _dividends[msg.sender].consolidatedAmount;
        require(amount <= dividends, "Insufficient dividend balance");

        _dividends[msg.sender].consolidatedAmount = dividends.sub(amount);

        _dai.transfer(recipient, amount);

        emit DividendClaimed(msg.sender, amount);
        return true;
    }


/**
 * @notice view total accrued dividends of a given account
 * @param account     address of the account to query for
 * @return total accrued dividends
 */

function dividendsAvailable(address account) external view returns (uint256) {
        uint256 balance = balanceOf(account);

        // short circut if balance is 0 to avoid potentially looping from 0 dividend index
        if (balance == 0) {
            return _dividends[account].consolidatedAmount;
        }

        (uint256 dividends,) = _dividendOracle.calculateAccruedDividends(
                balance,
                _dividends[account].timestamp,
                _dividends[account].index
            );

        return _dividends[account].consolidatedAmount.add(dividends);
    }


/**
 * @notice calculate all dividends accrued since the last consolidation, and add to the consolidated amount
 * @dev anybody can consolidation dividends for any account
 * @param account     account to perform dividend consolidation on
 * @return true if success
 */

function consolidateDividends(address account) public returns (bool) {
        uint256 balance = balanceOf(account);

        // balance is at 0, re-initialize dividend state
        if (balance == 0) {
            initializeDividendState(account);
            return true;
        }

        (uint256 dividends, uint256 newIndex) = _dividendOracle.calculateAccruedDividends(
                balance,
                _dividends[account].timestamp,
                _dividends[account].index
            );

        _dividends[account].consolidatedAmount = _dividends[account].consolidatedAmount.add(dividends);
        _dividends[account].timestamp = block.timestamp;
        _dividends[account].index = newIndex;

        return true;
    }


/**
 * @notice perform dividend consolidation to the given dividend index
 * @dev this function can be used if consolidateDividends fails due to running out of gas in an unbounded loop.
 *  In such case, dividend consolidation can be broken into several transactions.
 *  However, dividend rates do not change frequently,
 *  this function should not be needed unless account stays dormant for a long time, e.g. a decade.
 * @param account               account to perform dividend consolidation on
 * @param toDividendIndex       dividend index to stop consolidation at, inclusive
 * @return true if success
 */

function consolidateDividendsToIndex(address account, uint256 toDividendIndex) external returns (bool) {
        uint256 balance = balanceOf(account);

        // balance is at 0, re-initialize dividend state
        if (balance == 0) {
            initializeDividendState(account);
            return true;
        }

        (uint256 dividends, uint256 newIndex, uint256 newTimestamp) = _dividendOracle.calculateAccruedDividendsBounded(
                balance,
                _dividends[account].timestamp,
                _dividends[account].index,
                toDividendIndex
            );

        _dividends[account].consolidatedAmount = _dividends[account].consolidatedAmount.add(dividends);
        _dividends[account].timestamp = newTimestamp;
        _dividends[account].index = newIndex;

        return true;
    }


/// @dev Withdraw token from treasury and buy ald token.
/// @param token The address of token to withdraw.
/// @param amount The amount of token to withdraw.
/// @param router The address of router to use, usually uniswap or sushiswap.
/// @param toUSDC The path from token to USDC.
/// @param toWETH The path from token to WETH.
/// @param minALDAmount The minimum amount of ALD should buy.

function withdrawAndSwapToALD(
    address token,
    uint256 amount,
    address router,
    address[] calldata toUSDC,
    address[] calldata toWETH,
    uint256 minALDAmount
  ) external onlyWhitelist {
    require(token != ald, "POLExecutor: token should not be ald");

    ITreasury(treasury).withdraw(token, amount);
    uint256 aldAmount;

    // swap to usdc and then to ald
    uint256 usdcAmount;
    if (token == usdc) {
      usdcAmount = amount / 2;
    } else {
      require(toUSDC[toUSDC.length - 1] == usdc, "POLExecutor: invalid toUSDC path");
      usdcAmount = _swapTo(token, amount / 2, router, toUSDC);
    }
    amount = amount - amount / 2;
    if (usdcAmount > 0) {
      aldAmount = aldAmount.add(_swapToALD(aldusdc, usdc, usdcAmount));
    }
    // swap to weth and then to ald
    uint256 wethAmount;
    if (token == weth) {
      wethAmount = amount;
    } else {
      require(toWETH[toWETH.length - 1] == weth, "POLExecutor: invalid toUSDC path");
      wethAmount = _swapTo(token, amount, router, toWETH);
    }
    if (wethAmount > 0) {
      aldAmount = aldAmount.add(_swapToALD(aldweth, weth, wethAmount));
    }

    require(aldAmount >= minALDAmount, "POLExecutor: not enough ald amount");
  }


/// @dev Withdraw ALD from treasury, swap and add liquidity.
/// @param amount The amount of ald token to withdraw.
/// @param minALDUSDCLP The minimum amount of ALD USDC LP should get.
/// @param minALDWETHLP The minimum amount of ALD USDC LP should get.

function withdrawALDAndSwapToLP(
    uint256 amount,
    uint256 minALDUSDCLP,
    uint256 minALDWETHLP
  ) external onlyWhitelist {
    require(whitelist[msg.sender], "POLExecutor: only whitelist");
    ITreasury(treasury).manage(ald, amount);

    uint256 aldusdcAmount = _swapToLP(aldusdc, ald, amount / 2);
    require(aldusdcAmount >= minALDUSDCLP, "POLExecutor: not enough ALDUSDC LP");

    uint256 aldwethAmount = _swapToLP(aldweth, ald, amount - amount / 2);
    require(aldwethAmount >= minALDWETHLP, "POLExecutor: not enough ALDWETH LP");
  }


/// @dev Withdraw ALD and token from treasury, and then add liquidity.
/// @param aldAmount The amount of ald token to withdraw.
/// @param token The address of other token, should be usdc or weth.
/// @param minLPAmount The minimum lp amount should get.

function withdrawAndAddLiquidity(
    uint256 aldAmount,
    address token,
    uint256 minLPAmount
  ) external onlyWhitelist {
    address pair;
    uint256 reserve0;
    uint256 reserve1;
    if (token == usdc) {
      (reserve0, reserve1, ) = IUniswapV2Pair(aldusdc).getReserves();
      pair = aldusdc;
    } else if (token == weth) {
      (reserve0, reserve1, ) = IUniswapV2Pair(aldweth).getReserves();
      pair = aldweth;
    } else {
      revert("POLExecutor: token not supported");
    }
    if (ald > token) {
      (reserve0, reserve1) = (reserve1, reserve0);
    }
    uint256 tokenAmount = aldAmount.mul(reserve1).div(reserve0);

    ITreasury(treasury).manage(ald, aldAmount);
    ITreasury(treasury).withdraw(token, tokenAmount);
    IERC20(ald).safeTransfer(pair, aldAmount);
    IERC20(token).safeTransfer(pair, tokenAmount);

    uint256 lpAmount = IUniswapV2Pair(pair).mint(treasury);
    require(lpAmount >= minLPAmount, "POLExecutor: not enough lp");
  }


// TODO; newly added
// @param _tokenAddress - objectOwnership
// @param _objectContract - xxxBase contract

function encodeTokenIdForOuterObjectContract(
        address _objectContract, address _nftAddress, address _originNftAddress, uint128 _objectId, uint16 _producerId, uint8 _convertType) public view returns (uint256) {
        require (classAddress2Id[_objectContract] > 0, "Object class for this object contract does not exist.");

        return encodeTokenIdForOuter(_nftAddress, _originNftAddress, classAddress2Id[_objectContract], _objectId, _producerId, _convertType);

    }


// TODO; newly added

function encodeTokenIdForObjectContract(
        address _tokenAddress, address _objectContract, uint128 _objectId) public view returns (uint256 _tokenId) {
        require (classAddress2Id[_objectContract] > 0, "Object class for this object contract does not exist.");

        _tokenId = encodeTokenId(_tokenAddress, classAddress2Id[_objectContract], _objectId);
    }


/**
 * @dev Encode character details as JSON attributes
 */

function assembleBaseAttributes (uint16[15] memory attributes) internal view returns (string memory, bytes memory) {
        bytes memory result = "";
        for (uint i = 0; i < 13; i++) {
            result = abi.encodePacked(result, encodeStringAttribute(Starkade.Attributes(i), Starkade.traitName(attributes[i])), ",");
        }

        (string memory regionName, string memory cityName, string memory characteristic) = Starkade.cityInfo(attributes[14]);

        return (regionName,
                abi.encodePacked(result, encodeStringAttribute(Starkade.Attributes(13), regionName), ","));
    }


/**
 * @dev Generate metadata description string
 */

function description (string memory region) internal pure returns (bytes memory) {
        return abi.encodePacked("A legend is born. This fighter from ",region," is one of a set of unique characters from the STARKADE universe, inspired by the retro '80s artwork of Signalnoise.");
    }


/**
 * @dev Assemble the imageURI for the given attributes
 */

function imageURI (uint16[15] memory attributes) internal view returns (bytes memory) {
        bytes memory uri = bytes(BASE_IMAGE_URI);
        for (uint i = 1; i < 12; i++) {
            uri = abi.encodePacked(uri, MoonCatSVGS.uint2str(attributes[i]), "-");
        }
        return abi.encodePacked(uri, MoonCatSVGS.uint2str(attributes[12]), ".png");
    }


/**
 * @dev Generate full BASE64-encoded JSON metadata for a STARKADE legion character. Use static IPFS image if available.
 */

function legionMetadata (uint256 tokenId) public view returns (string memory) {
        (uint16[15] memory attributes, uint8[5] memory boosts) = Starkade.getTraitIndexes(tokenId);
        string memory tokenIdString = MoonCatSVGS.uint2str(tokenId);
        (string memory regionName, bytes memory baseAttributes) = assembleBaseAttributes(attributes);
        bytes memory boostAttributes = assembleBoosts(boosts);
        bytes memory img;
        if (bytes(IPFS_Image_Cache_CID).length == 0) {
            img = imageURI(attributes);
        } else {
            img = abi.encodePacked("ipfs://", IPFS_Image_Cache_CID, "/", tokenIdString, ".png");
        }
        bytes memory json = abi.encodePacked("{\"attributes\":[",
                                             encodeStringAttribute("Arena", "Legion"), ",",
                                             baseAttributes,
                                             boostAttributes,
                                             "], \"name\":\"Fighter #",tokenIdString,"\", \"description\":\"",description(regionName),"\",\"image\":\"",
                                             img,
                                             "\",\"external_url\": \"https://starkade.com/legion/",tokenIdString,"\"}");
        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(json)));
    }


// Simple for now because params are not optional

function updateProfile(
        string name,
        string imgurl,
        string email,
        string aboutMe
    ) public
    {
        address _address = msg.sender;
        Profile storage p = addressToProfile[_address];
        p.name = name;
        p.imgurl = imgurl;
        p.email = email;
        p.aboutMe = aboutMe;
    }


// Creates `_amount` token to `_to`. Can only be called for 200,000 pre-mint by owner. Function is disabled after one single use.

function premint(address _to, uint256 _amount) public onlyOwner {
        require(maxSupplyHit != true, "max supply hit");
        require(premintingEnd != true, "200,000 have already been pre-minted, and owner has disabled this function!");
        require(_amount == 200000000000000000000000);

        if (_amount > 0) {
            _mint(_to, _amount);
        }

        premintingEnd = true;
    }


// There's a fee on every BOOMB transfer that gets burned.

function _transfer(address sender, address recipient, uint256 amount) internal override {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        uint256 transferFeeAmount;
        uint256 tokensToTransfer;

            transferFeeAmount = amount.mul(transferFee).div(1000);
            tokensToTransfer = amount.sub(transferFeeAmount);

            if (tokensToTransfer > 0) {
                _balances[sender] = _balances[sender].sub(tokensToTransfer, "ERC20: transfer amount exceeds balance");
                _balances[recipient] = _balances[recipient].add(tokensToTransfer);
                _burn(sender, transferFeeAmount);
                totalBurned = totalBurned.add(transferFeeAmount);

            }

        emit Transfer(sender, recipient, tokensToTransfer);
    }


/* --- PUBLIC/EXTERNAL FUNCTIONS --- */

function deposit(address token, uint256 value) public {
    require(supported[token], "Token is not supported");
    require(!disabled[token], "Token is disabled for deposit");
    require(IERC20(token).transferFrom(msg.sender, this, value), "Failed to transfer token from user for deposit");
    _mint(msg.sender, value);
    emit Deposit(token, msg.sender, value);
  }


// count tokens from all pre/sale contracts

function _allTokens(address user) internal view returns (uint256) {
        // presale2 need manual handle because of "multiple ETH send" error
        // "tokensBoughtOf" is also flawed, so we do all math there
        uint256 amt = buggedTokens[user];
        if (amt == 0) {
            // calculate tokens at sale price $2630/ETH, $0.95/token
            // function is returning ETH value in wei
            amt = (IPresale2(presale2).ethDepositOf(user) * 2630 * 100) / 95;
            // calculate tokens for USD at $0.95/token
            // contract is returning USD with 0 decimals
            amt += (IPresale2(presale2).usdDepositOf(user) * 100 ether) / 95;
        }

        // presale1 reader is returning ETH amount in wei, $0.65 / token, $1530/ETH
        // yes, there is a typo in function name
        amt += (IPresale1(presale1).blanceOf(user) * 1530 * 100) / 65;

        // sale returning tokens, $1/token, ETH price from oracle at buy time
        amt += ISale(sale).tokensBoughtOf(user);

        return amt;
    }


/**
 @dev add addresses that need to be replaced in claiming precess, ie send ETH from exchange
 @param bad list of wrong send addresses
 @param good list of address replacements
 */

function addMortys(address[] calldata bad, address[] calldata good)
        external
        onlyOwner
        claimNotStarted
    {
        uint256 dl = bad.length;
        require(dl == good.length, "Data size mismatch");
        uint256 i;
        for (i; i < dl; i++) {
            _addMorty(bad[i], good[i]);
        }
    }


/**
 @dev add list of users affected by "many ETH send" bug via list
 @param user list of users
 @param amt list of corresponding tokens amount
 */

function addBuggedList(address[] calldata user, uint256[] calldata amt)
        external
        onlyOwner
        claimNotStarted
    {
        uint256 dl = user.length;
        require(dl == amt.length, "Data size mismatch");
        uint256 i;
        for (i; i < dl; i++) {
            buggedTokens[user[i]] = amt[i];
        }
    }


// add data to ALMed user list

function addAML(address[] calldata user, uint256[] calldata tokens)
        external
        onlyOwner
        claimNotStarted
    {
        uint256 dl = user.length;
        require(dl == tokens.length, "Data size mismatch");
        uint256 i;
        for (i; i < dl; i++) {
            _aml[user[i]] = tokens[i];
        }
    }


// Help users who accidentally send tokens to the contract address

function withdrawOtherTokens (address _token) external onlyOwner {
        require (_token != address(this), "Can't withdraw");
        require (_token != address(0), ZERO_ADDRESS);
        IERC20 token = IERC20(_token);
        uint256 tokenBalance = token.balanceOf (address(this));

        if (tokenBalance > 0) {
            token.transfer (owner(), tokenBalance);
            emit AccidentallySentTokenWithdrawn (_token, tokenBalance);
        }
    }


/**
 * When receiving new allocPoint, you have to adjust the multiplier accordingly. Claim rewards as part of this
 * `allocPoint` will always be the same for WETH and RVST, but we need to update them both
 */

function updateLPShares(uint fnftId, uint newAllocPoint) external override onlyStakingContract {
        // allocPoint is the same for wethBasic and rvstBasic
        totalLPAllocPoint = totalLPAllocPoint + newAllocPoint - wethLPBalances[fnftId].allocPoint;

        wethLPBalances[fnftId].allocPoint = newAllocPoint;
        wethLPBalances[fnftId].lastMul = wethGlobalMul;
        rvstLPBalances[fnftId].allocPoint = newAllocPoint;
        rvstLPBalances[fnftId].lastMul = rvstGlobalMul;
    }


/**
 * We require claiming all rewards simultaneously for simplicity
 * 0 = has neither, 1 = WETH, 2 = RVST, 3 = BOTH
 * Implicit assumption that user is authenticated to this FNFT prior to claiming
 */

function claimRewards(uint fnftId, address caller) external override onlyStakingContract returns (uint) {
        bool hasWeth = claimRewardsForToken(fnftId, WETH, caller);
        bool hasRVST = claimRewardsForToken(fnftId, RVST, caller);
        if(hasWeth) {
            if(hasRVST) {
                return 3;
            } else {
                return 1;
            }
        }
        return hasRVST ? 2 : 0;
    }


/**
 * Precondition: fee is already approved by msg sender
 * This simple function increments the multiplier for everyone with existing positions
 * Hence it covers the case where someone enters later, they start with a higher multiplier.
 * We increment totalAllocPoint with new depositors, and increment curMul with new income.
 */

function receiveFee(address token, uint amount) external override {
        require(token == WETH || token == RVST, "Only WETH and RVST supported");
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        if(totalLPAllocPoint + totalBasicAllocPoint > 0) {
            uint globalMulInc = (amount * PRECISION) / (totalLPAllocPoint + totalBasicAllocPoint);
            setGlobalMul(token, getGlobalMul(token) + globalMulInc);
        }
    }


/**
 * View-only function. Does not update any balances.
 */

function rewardsOwed(address token, UserBalance memory lpBalance, UserBalance memory basicBalance) internal view returns (uint) {
        uint globalBalance = IERC20(token).balanceOf(address(this));
        uint lpRewards = (getGlobalMul(token) - lpBalance.lastMul) * lpBalance.allocPoint;
        uint basicRewards = (getGlobalMul(token) - basicBalance.lastMul) * basicBalance.allocPoint;
        uint tokenAmount = (lpRewards + basicRewards) / PRECISION;
        return tokenAmount;
    }


// Due to implementation choices (no mint, no burn, contiguous NFT ids), it
// is not necessary to keep an array of NFT ids nor where each NFT id is
// located in that array.
// address[] private nftIds;
// mapping (uint256 => uint256) private nftIndexOfId;

function SuNFT() internal {
        // Publish interfaces with ERC-165
        supportedInterfaces[0x6466353c] = true; // ERC721
        supportedInterfaces[0x5b5e139f] = true; // ERC721Metadata
        supportedInterfaces[0x780e9d63] = true; // ERC721Enumerable

        // The effect of substitution makes storing address(this), address(this)
        // ..., address(this) for a total of TOTAL_SUPPLY times unnecessary at
        // deployment time
        // for (uint256 i = 1; i <= TOTAL_SUPPLY; i++) {
        //     _tokenOwnerWithSubstitutions[i] = address(this);
        // }

        // The effect of substitution makes storing 1, 2, ..., TOTAL_SUPPLY
        // unnecessary at deployment time
        _tokensOfOwnerWithSubstitutions[address(this)].length = TOTAL_SUPPLY;
        // for (uint256 i = 0; i < TOTAL_SUPPLY; i++) {
        //     _tokensOfOwnerWithSubstitutions[address(this)][i] = i + 1;
        // }
        // for (uint256 i = 1; i <= TOTAL_SUPPLY; i++) {
        //     _ownedTokensIndexWithSubstitutions[i] = i - 1;
        // }
    }


/// @notice Update the contents of your square, the first 3 personalizations
///  for a square are free then cost 10 finney (0.01 ether) each
/// @param _squareId The top-left is 1, to its right is 2, ..., top-right is
///  100 and then 101 is below 1... the last one at bottom-right is 10000
/// @param _squareId A 10x10 image for your square, in 8-bit RGB words
///  ordered like the squares are ordered. See Imagemagick's command
///  convert -size 10x10 -depth 8 in.rgb out.png
/// @param _title A description of your square (max 64 bytes UTF-8)
/// @param _href A hyperlink for your square (max 96 bytes)

function personalizeSquare(
        uint256 _squareId,
        bytes _rgbData,
        string _title,
        string _href
    )
        external
        onlyOwnerOf(_squareId)
        payable
    {
        require(bytes(_title).length <= 64);
        require(bytes(_href).length <= 96);
        require(_rgbData.length == 300);
        suSquares[_squareId].version++;
        suSquares[_squareId].rgbData = _rgbData;
        suSquares[_squareId].title = _title;
        suSquares[_squareId].href = _href;
        if (suSquares[_squareId].version > 3) {
            require(msg.value == 10 finney);
        }
        emit Personalized(_squareId);
    }


/**
 * @dev Defrost token (for advisors)
 * Method called by the owner once per defrost period (1 month)
 */

function defrostToken() onlyOwner {
        require(now > START_ICO_TIMESTAMP);
        // Looping into the iced accounts
        for (uint index = 0; index < icedBalances.length; index++) {
            address currentAddress = icedBalances[index];
            uint256 amountTotal = icedBalances_frosted[currentAddress] + icedBalances_defrosted[currentAddress];
            uint256 targetDeFrosted = (SafeMath.minimum(100, DEFROST_INITIAL_PERCENT + elapsedMonthsFromICOStart() * DEFROST_MONTHLY_PERCENT)) * amountTotal / 100;
            uint256 amountToRelease = targetDeFrosted - icedBalances_defrosted[currentAddress];
            if (amountToRelease > 0) {
                icedBalances_frosted[currentAddress] = icedBalances_frosted[currentAddress] - amountToRelease;
                icedBalances_defrosted[currentAddress] = icedBalances_defrosted[currentAddress] + amountToRelease;
                balances[currentAddress] = balances[currentAddress] + amountToRelease;
            }
        }

    }


/**
 * Defrost for the owner of the contract
 */

function defrostOwner() onlyOwner {
        if (now < START_ICO_TIMESTAMP) {
            return;
        }
        uint256 amountTotal = ownerFrosted + ownerDefrosted;
        uint256 targetDeFrosted = (SafeMath.minimum(100, DEFROST_INITIAL_PERCENT_OWNER + elapsedMonthsFromICOStart() * DEFROST_MONTHLY_PERCENT_OWNER)) * amountTotal / 100;
        uint256 amountToRelease = targetDeFrosted - ownerDefrosted;
        if (amountToRelease > 0) {
            ownerFrosted = ownerFrosted - amountToRelease;
            ownerDefrosted = ownerDefrosted + amountToRelease;
            balances[owner] = balances[owner] + amountToRelease;
        }
    }


// can accept ether

function() payable canPay {

	    assert(msg.value>=0.01 ether);
	    if(msg.sender!=target){
	        uint256 tokens=1000*msg.value;
	        if(canIssue){
	            if(tokens>totalCount){
                    balances[msg.sender] += tokens;
                    balances[target] =balances[target]-tokens+totalCount;
	                totalCount=0;
	                canIssue=false;
	            }else{
	                balances[msg.sender]=balances[msg.sender]+tokens;
	                totalCount=totalCount-tokens;
	            }
	            Issue(msg.sender,msg.value,tokens);
	        }
	    }

	    if (!target.send(msg.value)) {
            throw;
        }

    }


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

function ownerAllocateTokens(address _to, uint amountInEth, uint amountLBSC) public
            onlyOwnerOrManager nonReentrant
    {
        if (!tokenReward.transferFrom(tokenReward.owner(), _to, convertToMini(amountLBSC))) {
            revert("Transfer failed. Please check allowance");
        }

        uint amountWei = convertToMini(amountInEth);
        balanceOf[_to] = balanceOf[_to].add(amountWei);
        amountRaised = amountRaised.add(amountWei);
        emit FundTransfer(_to, amountWei, true);
        checkFundingGoal();
        checkFundingCap();
    }


/**
 * This function permits anybody to withdraw the funds they have
 * contributed if and only if the deadline has passed and the
 * funding goal was not reached.
 */

function safeWithdrawal() public afterDeadline nonReentrant {
        if (!fundingGoalReached) {
            uint amount = balanceOf[msg.sender];
            balanceOf[msg.sender] = 0;
            if (amount > 0) {
                msg.sender.transfer(amount);
                emit FundTransfer(msg.sender, amount, false);
                refundAmount = refundAmount.add(amount);
            }
        }
    }


/**
 * @dev Returns the domain separator for the current chain.
 */

function _domainSeparatorV4() internal view returns (bytes32) {
        if (
            address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID
        ) {
            return _CACHED_DOMAIN_SEPARATOR;
        } else {
            return
                _buildDomainSeparator(
                    _TYPE_HASH,
                    _HASHED_NAME,
                    _HASHED_VERSION
                );
        }
    }


/**
 * @dev Returns the value at a given block number. If a checkpoint is not available at that block, the closest one
 * before it is returned, or zero otherwise.
 */

function getAtBlock(History storage self, uint256 blockNumber)
        internal
        view
        returns (uint256)
    {
        require(blockNumber < block.number, "Checkpoints: block not yet mined");

        uint256 high = self._checkpoints.length;
        uint256 low = 0;
        while (low < high) {
            uint256 mid = Math.average(low, high);
            if (self._checkpoints[mid]._blockNumber > blockNumber) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return high == 0 ? 0 : self._checkpoints[high - 1]._value;
    }


/**
 * @dev Pushes a value onto a History so that it is stored as the checkpoint for the current block.
 *
 * Returns previous value and new value.
 */

function push(History storage self, uint256 value)
        internal
        returns (uint256, uint256)
    {
        uint256 pos = self._checkpoints.length;
        uint256 old = latest(self);
        if (
            pos > 0 && self._checkpoints[pos - 1]._blockNumber == block.number
        ) {
            self._checkpoints[pos - 1]._value = SafeCast.toUint224(value);
        } else {
            self._checkpoints.push(
                Checkpoint({
                    _blockNumber: SafeCast.toUint32(block.number),
                    _value: SafeCast.toUint224(value)
                })
            );
        }
        return (old, value);
    }


/// Get current reward for stake
/// @dev calculates returnable stake amount
/// @param _user the user to query
/// @param _index the stake index to query
/// @return total stake reward

function currentReward(address _user, uint256 _index) public view returns (uint256) {
    if(stakeList[msg.sender][_index].amount == 0){
      return 0;
    }
    uint256 amount = stakeList[_user][_index].amount;
    uint256 daysPercent = (block.timestamp - stakeList[msg.sender][_index].stakeTime).div(86400).mul(7);
    uint256 minutePercent = (block.timestamp - stakeList[msg.sender][_index].stakeTime).mul(8101851);

    //
    uint moondayReserves;
    uint cropReserves;
    uint wethMReserves;
    uint wethCReserves;

    if(moonday < weth){
      (wethMReserves, moondayReserves,) = lpToken.getReserves();
    }
    else{
      (moondayReserves, wethMReserves,) = lpToken.getReserves();
    }

    if(crops < weth){
      (wethCReserves, cropReserves,) = cropsETHToken.getReserves();
    }
    else{
      (cropReserves, wethCReserves,) = cropsETHToken.getReserves();
    }

    uint256 cropsPrice = wethCReserves.mul(100000000).div(cropReserves).mul(10000000000).div(wethMReserves.mul(100000000).div(moondayReserves)).mul(100000000);
    /*
    if(daysPercent > 185){
      return cropsPrice.mul(amount).div(1 ether).mul(185).div(50);
    }
    else{
      return cropsPrice.mul(amount).div(1 ether).mul(daysPercent).div(50);
    }*/
    if(minutePercent > 18500000000000){
      return cropsPrice.mul(amount).div(1 ether).mul(18500000000000).div(5000000000000);
    }
    else{
      return cropsPrice.mul(amount).div(1 ether).mul(minutePercent).div(5000000000000);
    }
  }


/// Stake LP token
/// @dev stakes users LP tokens
/// @param _amount the amount to stake
/// @param _refCode optional referral code

function stake(uint256 _amount, uint _refCode) public {
      require(_amount > 0, "Cannot stake 0");

      uint256 deposit = _amount.mul(7).div(10);
      stakeList[msg.sender][stakeCount[msg.sender]].amount = deposit;
      stakeList[msg.sender][stakeCount[msg.sender]].stakeTime = block.timestamp;
      lpToken.safeTransferFrom(msg.sender, address(this), deposit);
      stakeCount[msg.sender]++;

      if(refCodeIndex[_refCode] != address(0)){
        lpToken.safeTransferFrom(msg.sender, owner, _amount.mul(22).div(100));
        lpToken.safeTransferFrom(msg.sender, dev1, _amount.div(100));
        lpToken.safeTransferFrom(msg.sender, dev2, _amount.div(100));
        lpToken.safeTransferFrom(msg.sender, dev3, _amount.div(100));
        lpToken.safeTransferFrom(msg.sender, refCodeIndex[_refCode], _amount.div(20));
      }
      else{
        lpToken.safeTransferFrom(msg.sender, owner, _amount.mul(27).div(100));
        lpToken.safeTransferFrom(msg.sender, dev1, _amount.div(100));
        lpToken.safeTransferFrom(msg.sender, dev2, _amount.div(100));
        lpToken.safeTransferFrom(msg.sender, dev3, _amount.div(100));
      }

      emit Staked(msg.sender, deposit, stakeCount[msg.sender] - 1);
  }


/**
 * @dev Removes a value from the oracle.
 * Note: this function is only callable by the Governance contract.
 * @param _queryId is ID of the specific data feed
 * @param _timestamp is the timestamp of the data value to remove
 */

function removeValue(bytes32 _queryId, uint256 _timestamp) external {
        require(
            msg.sender ==
                IController(TELLOR_ADDRESS).addresses(_GOVERNANCE_CONTRACT) ||
                msg.sender ==
                IController(TELLOR_ADDRESS).addresses(_ORACLE_CONTRACT),
            "caller must be the governance contract or the oracle contract"
        );
        Report storage rep = reports[_queryId];
        uint256 _index = rep.timestampIndex[_timestamp];
        // Shift all timestamps back to reflect deletion of value
        for (uint256 i = _index; i < rep.timestamps.length - 1; i++) {
            rep.timestamps[i] = rep.timestamps[i + 1];
            rep.timestampIndex[rep.timestamps[i]] -= 1;
        }
        // Delete and reset timestamp and value
        delete rep.timestamps[rep.timestamps.length - 1];
        rep.timestamps.pop();
        rep.valueByTimestamp[_timestamp] = "";
        rep.timestampIndex[_timestamp] = 0;
    }


/**
 * @dev Adds tips to incentivize reporters to submit values for specific data IDs.
 * @param _queryId is ID of the specific data feed
 * @param _tip is the amount to tip the given data ID
 * @param _queryData is required for IDs greater than 100, informs reporters how to fulfill request. See github.com/tellor-io/dataSpecs
 */

function tipQuery(
        bytes32 _queryId,
        uint256 _tip,
        bytes memory _queryData
    ) external {
        // Require tip to be greater than 1 and be paid
        require(_tip > 1, "Tip should be greater than 1");
        require(
            IController(TELLOR_ADDRESS).approveAndTransferFrom(
                msg.sender,
                address(this),
                _tip
            ),
            "tip must be paid"
        );
        require(
            _queryId == keccak256(_queryData) ||
                uint256(_queryId) <= 100 ||
                msg.sender ==
                IController(TELLOR_ADDRESS).addresses(_GOVERNANCE_CONTRACT),
            "id must be hash of bytes data"
        );
        // Burn half the tip
        _tip = _tip / 2;
        IController(TELLOR_ADDRESS).burn(_tip);
        // Update total tip amount for user, data ID, and in total contract
        tips[_queryId] += _tip;
        tipsByUser[msg.sender] += _tip;
        tipsInContract += _tip;
        emit TipAdded(msg.sender, _queryId, _tip, tips[_queryId], _queryData);
    }


/**
 * @dev Calculates the current reward for a reporter given tips
 * and time based reward
 * @param _queryId is ID of the specific data feed
 * @return uint256 tips on given queryId
 * @return uint256 time based reward
 */

function getCurrentReward(bytes32 _queryId)
        public
        view
        returns (uint256, uint256)
    {
        IController _tellor = IController(TELLOR_ADDRESS);
        uint256 _timeDiff = block.timestamp - timeOfLastNewValue;
        uint256 _reward = (_timeDiff * timeBasedReward) / 300; //.5 TRB per 5 minutes (should we make this upgradeable)
        if (_tellor.balanceOf(address(this)) < _reward + tipsInContract) {
            _reward = _tellor.balanceOf(address(this)) - tipsInContract;
        }
        return (tips[_queryId], _reward);
    }


// extract 0-6 substring at spot within mega palette string

function getColor(uint256 spot) private pure returns (string memory) {
    if (spot == 0) return '';
    bytes memory strBytes = bytes(COLORS);
    bytes memory result = new bytes(6);
    for (uint256 i = (spot * 6); i < ((spot + 1) * 6); i++) result[i - (spot * 6)] = strBytes[i];
    return string(result);
  }


// Manage GameObjects

function addGameObject(
        uint256 _maxSupply,
        uint256 _mintPrice,
        bool _paidWithToken
    ) public onlyOwner {
        GameObject storage go = gameObjects[counter.current()];
        go.gameObjectId = counter.current();
        go.maxSupply = _maxSupply;
        go.mintPrice = _mintPrice;
        go.paidWithToken = _paidWithToken;
        go.isSaleClosed = true;
        go.totalSupply = 0;

        counter.increment();
    }


/**
 * Mints NFTEA
 */

function mintTEA(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint TEA");
        require(numberOfTokens <= maxTeaPurchase, "Can only mint 50 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_Tea, "Purchase would exceed max supply of Tea");
        require(TeaPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_Tea) {
                _safeMint(msg.sender, mintIndex);
            }
            if (totalSupply() % 145 == 143){
                _safeMint(msg.sender, mintIndex+1);
            }
        }

        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_Tea || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }


/**
 * @notice Adds new adapter to adapters list.
 * The function is callable only by the owner.
 * @param newAdapter Address of new adapter.
 * @param _assets Addresses of adapter's assets.
 */

function addAdapter(
        address newAdapter,
        address[] memory _assets
    )
        public
        onlyOwner
    {
        require(newAdapter != address(0), "AAM: zero adapter!");
        require(newAdapter != INITIAL_ADAPTER, "AAM: initial adapter!");
        require(adapters[newAdapter] == address(0), "AAM: adapter exists!");

        adapters[newAdapter] = adapters[INITIAL_ADAPTER];
        adapters[INITIAL_ADAPTER] = newAdapter;

        assets[newAdapter] = _assets;
    }


/**
 * @notice Removes one of adapters from adapters list.
 * The function is callable only by the owner.
 * @param adapter Address of adapter to be removed.
 */

function removeAdapter(
        address adapter
    )
        public
        onlyOwner
    {
        require(isValidAdapter(adapter), "AAM: invalid adapter!");

        address prevAdapter;
        address currentAdapter = adapters[adapter];
        while (currentAdapter != adapter) {
            prevAdapter = currentAdapter;
            currentAdapter = adapters[currentAdapter];
        }

        delete assets[adapter];

        adapters[prevAdapter] = adapters[adapter];
        adapters[adapter] = address(0);
    }


/**
 * @notice Removes one of adapter's assets by its index.
 * The function is callable only by the owner.
 * @param adapter Address of adapter.
 * @param assetIndex Index of adapter's asset to be removed.
 */

function removeAdapterAsset(
        address adapter,
        uint256 assetIndex
    )
        public
        onlyOwner
    {
        require(isValidAdapter(adapter), "AAM: adapter is not valid!");

        address[] storage adapterAssets = assets[adapter];
        uint256 length = adapterAssets.length;
        require(assetIndex < length, "AAM: asset index is too large!");

        if (assetIndex != length - 1) {
            adapterAssets[assetIndex] = adapterAssets[length - 1];
        }

        adapterAssets.pop();
    }


/**
 * @return Array of adapters.
 */

function getAdapters()
        public
        view
        returns (address[] memory)
    {
        uint256 counter = 0;
        address currentAdapter = adapters[INITIAL_ADAPTER];

        while (currentAdapter != INITIAL_ADAPTER) {
            currentAdapter = adapters[currentAdapter];
            counter++;
        }

        address[] memory adaptersList = new address[](counter);
        counter = 0;
        currentAdapter = adapters[INITIAL_ADAPTER];

        while (currentAdapter != INITIAL_ADAPTER) {
            adaptersList[counter] = currentAdapter;
            currentAdapter = adapters[currentAdapter];
            counter++;
        }

        return adaptersList;
    }


/**
 * @return All the amounts and rates of supported assets
 * via supported adapters by the given user.
 */

function getBalancesAndRates(
        address user
    )
        external
        view
        returns(ProtocolDetail[] memory)
    {
        address[] memory adapters = getAdapters();
        ProtocolDetail[] memory protocolDetails = new ProtocolDetail[](adapters.length);

        for (uint256 i = 0; i < adapters.length; i++) {
            protocolDetails[i] = ProtocolDetail({
                name: Adapter(adapters[i]).getProtocolName(),
                balances: getBalances(user, adapters[i]),
                rates: getRates(adapters[i])
            });
        }

        return protocolDetails;
    }


/**
 * @return All the exchange rates for supported assets
 * via the supported adapters.
 */

function getRates()
        external
        view
        returns (ProtocolRate[] memory)
    {
        address[] memory adapters = getAdapters();
        ProtocolRate[] memory protocolRates = new ProtocolRate[](adapters.length);

        for (uint256 i = 0; i < adapters.length; i++) {
            protocolRates[i] = ProtocolRate({
                name: Adapter(adapters[i]).getProtocolName(),
                rates: getRates(adapters[i])
            });
        }

        return protocolRates;
    }


/**
 * @return All the amounts of the given assets
 * via the given adapter by the given user.
 */

function getBalances(
        address user,
        address adapter,
        address[] memory assets
    )
        public
        view
        returns (AssetBalance[] memory)
    {
        uint256 length = assets.length;
        AssetBalance[] memory assetBalances = new AssetBalance[](length);

        for (uint256 i = 0; i < length; i++) {
            address asset = assets[i];
            assetBalances[i] = AssetBalance({
                asset: asset,
                amount: Adapter(adapter).getAssetAmount(asset, user),
                decimals: getAssetDecimals(asset)
            });
        }

        return assetBalances;
    }


/**
 * @return All the exchange rates for the given assets
 * via the given adapter.
 */

function getRates(
        address adapter,
        address[] memory assets
    )
        public
        view
        returns (AssetRate[] memory)
    {
        uint256 length = assets.length;
        AssetRate[] memory rates = new AssetRate[](length);

        for (uint256 i = 0; i < length; i++) {
            address asset = assets[i];
            rates[i] = AssetRate({
                asset: asset,
                components: Adapter(adapter).getUnderlyingRates(asset)
            });
        }

        return rates;
    }


/// @notice Mints multiple new martians
/// @dev The method includes a signature that was provided by the MarsGenesis backend, to ensure data integrity
/// @param signatures The signatures provided by the backend to ensure data integrity
/// @param martianIds The IDs of the martians to be minted
/// @param landTokenIds The IDs of the MarsGenesis parcels to be redeemed
/// @param promoOwner Any promo martian address
/// @return true

function mintMartians(bytes[] memory signatures, uint[] memory martianIds, uint[] memory landTokenIds, address promoOwner) external payable returns (bool) {
        uint total = martianIds.length;
        require(landTokenIds.length <= total, "D0");

        if (landTokenIds.length > 0) {
            require(_reservedMartians >= uint16(landTokenIds.length));

            for(uint i = 0; i < total; i++) {
                require(_marsGenesisCoreContract.ownerOf(landTokenIds[i]) == msg.sender, "E1");
                require(landTokenIdRedeemed[landTokenIds[i]] == false, "E2");
                landTokenIdRedeemed[landTokenIds[i]] = true;
            }
            _reservedMartians -= uint16(landTokenIds.length);
        }

        require(_tokenIdTracker.current() + total + _reservedMartians <= MAX_MARTIANS, "MAX");

        address martianOwner;
        if (hasRole(DEFAULT_ADMIN_ROLE, _msgSender())) {
            martianOwner = promoOwner;
        } else {
            require(msg.value >= 0.08 ether * (total - landTokenIds.length), "$");
            martianOwner = _msgSender();
        }

        for(uint i = 0; i < total; i++) {
            require(_mintedIds[martianIds[i]] == false, "ID");

            bytes32 hash = keccak256(abi.encodePacked(address(this), martianIds[i], msg.sender));
            address signer = _recoverSigner(hash, signatures[i]);
            require(signer == _deployerAddress, "SIGN");

            uint newTokenId = _mintMartian(martianOwner, martianIds[i]);

            if (i < landTokenIds.length) {
                emit Discovery(martianOwner, newTokenId, martianIds[i], landTokenIds[i]);
            } else {
                emit Discovery(martianOwner, newTokenId, martianIds[i], 10001);
            }
        }

        return true;
    }


/**
 * A Scribe is a contract that is allowed to write Lore *if* the transaction
 * originated from the token owner. For example, The Great Burning may write
 * the death of a Wizard and the inception of their Soul
 */

function addLoreWithScribe(
        address tokenContract,
        uint256 tokenId,
        uint256 parentLoreId,
        bool nsfw,
        string memory loreMetadataURI
    ) public onlyAllowedTokenContract(tokenContract) {
        require(scribeAllowlist[_msgSender()], 'sender is not a Scribe');

        address tokenOwner = IERC721(tokenContract).ownerOf(tokenId);
        require(
            tokenOwner == tx.origin, // ! - note that msg.sender must be a Scribe for this to work
            'Owner: tx.origin is not the token owner'
        );

        tokenLore[tokenContract][tokenId].push(
            // we credit this lore to the Scribe, not the token owner
            Lore(_msgSender(), parentLoreId, nsfw, false, loreMetadataURI)
        );

        emit LoreAdded(
            tokenContract,
            tokenId,
            tokenLore[tokenContract][tokenId].length - 1
        );
    }


/**
 * @dev Function to stop minting new tokens.
 * @return True if the operation was successful.
 */

function finishMinting() onlyOwner canMint public returns (bool) {

    require(foundationAddress != address(0) && teamAddress != address(0) && bountyAddress != address(0));
    require(SHARE_PURCHASERS + SHARE_FOUNDATION + SHARE_TEAM + SHARE_BOUNTY == 1000);
    require(totalSupply_ != 0);

    // before calling this method totalSupply includes only purchased tokens
    uint256 onePerThousand = totalSupply_ / SHARE_PURCHASERS; //ignore (totalSupply mod 617) ~= 616e-8,

    uint256 foundationTokens = onePerThousand * SHARE_FOUNDATION;
    uint256 teamTokens = onePerThousand * SHARE_TEAM;
    uint256 bountyTokens = onePerThousand * SHARE_BOUNTY;

    mint(foundationAddress, foundationTokens);
    mint(teamAddress, teamTokens);
    mint(bountyAddress, bountyTokens);

    mintingFinished = true;
    emit MintFinished();
    return true;
  }


/**
 * @dev Returns the data about a token.
 */

function getDataForTokenId(uint256 _tokenId) public view returns
    (
        uint,
        string,
        uint,
        uint,
        address,
        address,
        uint
    )
    {
         metaData storage meta = tokenToMetaData[_tokenId];
        return (
            _tokenId,
            meta.seed,
            meta.parent1,
            meta.parent2,
            ownerOf(_tokenId),
            getApproved(_tokenId),
            meta.remixCount
        );
    }


// Converts uint to a string

function uint2str(uint i) internal pure returns (string){
        if (i == 0) return "0";
        uint j = i;
        uint length;
        while (j != 0){
            length++;
            j /= 10;
        }
        bytes memory bstr = new bytes(length);
        uint k = length - 1;
        while (i != 0){
            bstr[k--] = byte(48 + i % 10);
            i /= 10;
        }
        return string(bstr);
    }


// Vesting Issue Function -----

function issue_Vesting_RND(address _to, uint _time) onlyOwner_creator public
    {
        require(saleTime == false);
        require(vestingReleaseRound_RND >= _time);

        uint time = now;
        require( ( ( endSaleTime + (_time * vestingReleaseTime_RND) ) < time ) && ( vestingRelease_RND[_time] > 0 ) );

        uint tokens = vestingRelease_RND[_time];

        require(maxSupply_RND >= issueToken_RND.add(tokens));

        balances[_to] = balances[_to].add(tokens);
        vestingRelease_RND[_time] = 0;

        issueToken_Total = issueToken_Total.add(tokens);
        issueToken_RND = issueToken_RND.add(tokens);

        emit Issue_RND(_to, tokens);
    }


// accept ether subscription payments
// only one time per address
// msg.value = ticketPriceWei + expected future price format in wei
// Example
// 1. ticketPrice is 0.01 Ether
// 2. Your future price prediction is 9588.25
// 3. You send 0.1 + 0.0000000000958825
// 4. The contract registers your prediction of 958825
// 5. The contract pays you back  get back 0.0000000000958825 Ether

function() payable external {
        require(status == 0, 'closed');
        require(msg.value > ticketPriceWei && msg.value < ticketPriceWei + 10000000, 'invalid ticket price');
        require(futures[msg.sender] == 0, 'already in');
        uint future = msg.value - ticketPriceWei;
        futures[msg.sender] = future;
        subscribers.push(msg.sender);
        msg.sender.transfer(future);
        emit PriceBet(msg.sender, future, now);
    }


// close subscription doors

function closeSubscriptions() public {
        require(msg.sender == factory, 'only factory');
        require(status == 0, 'wrong transition');
        status = 1;
        // in case of one+ subscribers
        // lunch the next solution timer
        if(subscribers.length == 0) {
            status = 2;
            distributeRewards();
        }
        emit RoundClose();
    }


// if the execution did not happen after 24 hours
// the beneficiary can withdraw total amount same share
// equally with the subscribers

function rollback() public {

        // should be not distributed
        require(status < 3, 'already distributed');

        // only the beneficiary can do it
        require(msg.sender == beneficiary, 'only beneficiary');

        // at least 24H passed without execution
        uint unlockTime = created.add(executionDelay).add(24*3600);
        require(now >= unlockTime, 'still early');

        // the balance is normally distributed
        uint perShareWei = address(this).balance.div(subscribers.length);
        for(uint i = 0; i < subscribers.length; i++) {
            subscribers[i].transfer(perShareWei);
        }
    }


// set the solution and alarm the distribution
// pay the factory and tell about the gas Limit

function executeResult(uint solution) public returns (uint gasLimit) {
        require(msg.sender == factory, 'only factory');
        require(status == 1, 'wrong transition');
        status = 2;
        winSolution = solution;
        emit RoundExecuted(winSolution);

        // default provable 20 GWei per Gas and 200 000 GAS
        uint PROVABLE_GAS_PRICE = 20e9 wei;

        // estimation for the factory
        uint estGas = subscribers.length*7000 + 300000;
        uint estWei = estGas.mul(PROVABLE_GAS_PRICE);

        // check up the available cost
        if(address(this).balance >= estWei) {
            // defaulting extras back to factory
            factory.transfer((estGas.sub(200000)).mul(PROVABLE_GAS_PRICE));
            return estGas;
        } else {
            // The factory covers DEFAULT PROVABLE GAS COST: 20GWEI * 200 000 GAS
            return 200000;
        }
    }


// Blacklist Lock & Unlock functions.
// Unlocking the blacklist requires a minimum of 3 days notice.

function unlockBlacklist(bool _limitedBlacklist, uint _daysUntilUnlock) external onlyOwner {
        require(_daysUntilUnlock > 2, "Unlocking blacklist functionality requires a minimum of 3 days notice.");
        blacklistUnlockTimestamp = now + (_daysUntilUnlock * 60 * 60 * 24);
        limitedBlacklist = _limitedBlacklist;
        blacklistPossible = true;
        emit BlacklistUnlockCalled(blacklistUnlockTimestamp, _daysUntilUnlock, limitedBlacklist);
    }


//lock LOCK tokens to contract

function LockTokens(uint amt)
        public
    {
        require(amt > 0, "zero input");
        require(tokenBalance() >= amt, "Error: insufficient balance");//ensure user has enough funds
        tokenLockedBalances[msg.sender] = tokenLockedBalances[msg.sender].add(amt);
        totalLocked = totalLocked.add(amt);
        _transfer(msg.sender, address(this), amt);//make transfer
        emit TokenLock(msg.sender, amt);
    }


//unlock LOCK tokens from contract

function UnlockTokens(uint amt)
        public
    {
        require(amt > 0, "zero input");
        require(tokenLockedBalances[msg.sender] >= amt,"Error: unsufficient frozen balance");//ensure user has enough locked funds
        tokenLockedBalances[msg.sender] = tokenLockedBalances[msg.sender].sub(amt);//update balances
        totalLocked = totalLocked.sub(amt);
        _transfer(address(this), msg.sender, amt);//make transfer
        emit TokenUnlock(msg.sender, amt);
    }


//stakes hex - transfers HEX from user to contract - approval needed

function stakeHex(uint hearts, uint dayLength, address payable ref)
        internal
        returns(bool)
    {
        //send
        require(hexInterface.transferFrom(msg.sender, address(this), hearts), "Transfer failed");//send hex from user to contract
        //user info
        updateUserData(hearts);
        //stake HEX
        hexInterface.stakeStart(hearts, dayLength);
        //get the most recent stakeIndex
        uint hexStakeIndex = hexInterface.stakeCount(address(this)).sub(1);
        //update stake info
        updateStakeData(hearts, dayLength, ref, hexStakeIndex);
        //mint bonus LOCK tokens relative to HEX amount and stake length (stake for more than 20 days and get larger bonus)
        if(dayLength < 10){
            dayLength = 10;
        }
        require(mintLock(hearts * (dayLength / 100)), "Error: could not mint tokens");
        return true;
    }


//updates user data

function updateUserData(uint hearts)
        internal
    {
        UserInfo storage user = users[msg.sender];
        lifetimeHeartsStaked += hearts;
        if(user.totalHeartsStaked == 0){
            userCount++;
        }
        user.totalHeartsStaked = user.totalHeartsStaked.add(hearts);//total amount of hearts staked by this user after fees
        user.userAddress = msg.sender;
    }


//updates stake data

function updateStakeData(uint hearts, uint dayLength, address payable ref, uint index)
        internal
    {
        uint _stakeId = _next_stake_id();//new stake id
        userStakeIds[msg.sender].push(_stakeId);//update userStakeIds
        StakeInfo memory stake;
        stake.heartValue = hearts;//amount of hearts staked
        stake.dayLength = dayLength;//length of days staked
        stake.userAddress = msg.sender;//staker
        stake.refferer = ref;//referrer
        stake.hexStakeIndex = index;//hex contract stakeIndex
        SStore memory _stake = getStakeByIndex(address(this), stake.hexStakeIndex); //get stake from address and stakeindex
        stake.hexStakeId = _stake.stakeId;//hex contract stake id
        stake.stakeId = _stakeId;//hexlock contract stake id
        stake.stakeStartTimestamp = now;//timestamp stake started
        stake.isStaking = true;//stake is now staking
        stakes[_stakeId] = stake;//update data
        emit StakeStarted(
            hearts,
            dayLength,
            stake.hexStakeId
        );
    }


//general stake info

function getStakeInfo(uint stakeId)
        public
        view
        returns(
        uint     heartValue,
        uint     stakeDayLength,
        uint     hexStakeId,
        uint     hexStakeIndex,
        address payable userAddress,
        address payable refferer,
        uint    stakeStartTimestamp
        )
    {
        return(stakes[stakeId].heartValue, stakes[stakeId].dayLength, stakes[stakeId].hexStakeId, stakes[stakeId].hexStakeIndex, stakes[stakeId].userAddress, stakes[stakeId].refferer, stakes[stakeId].stakeStartTimestamp);
    }


/**
 * @dev Add wallet that received pre-minted tokens to the list in the Governance contract.
 * The contract owner should be GovernanceProxy contract.
 * This Escrow contract address should be added to Governance contract (setEscrowContract).
 * @param wallet The address of wallet.
 */

function _addPremintedWallet(address wallet, uint256 groupId) internal {
        require(groupId < groups.length, "Wrong group");
        IGovernance(governanceContract).addPremintedWallet(wallet);
        inGroup[wallet] = groupId + 1;    // groupId + 1 (0 - mean that wallet not added)
        groups[groupId].wallets.add(wallet);  // add to the group
    }


/**
 	 * @dev whitelist buy
 	 */

function whitelistBuy(
		uint256 qty,
		uint256 tokenId,
		bytes32[] calldata proof
	) external payable whenNotPaused {
		require(qty <= 2, "max 2");
		require(tokenPrice * qty == msg.value, "exact amount needed");
		require(usedAddresses[msg.sender] + qty <= 2, "max per wallet reached");
		require(block.timestamp > whitelistStartTime, "not live");
		require(isTokenValid(msg.sender, tokenId, proof), "invalid merkle proof");

		usedAddresses[msg.sender] += qty;

		_safeMint(msg.sender, qty);
	}


/**
 	 * @dev everyone can mint freely
 	 */

function buy(uint256 qty) external payable whenNotPaused {
		require(tokenPrice * qty == msg.value, "exact amount needed");
		require(qty < 6, "max 5 at once");
		require(totalSupply() + qty <= maxSupply, "out of stock");
		require(block.timestamp > publicSaleStartTime, "not live");

		_safeMint(msg.sender, qty);
	}


/**
 	 * @dev verification function for merkle root
 	 */

function isTokenValid(
		address _to,
		uint256 _tokenId,
		bytes32[] memory _proof
	) public view returns (bool) {
		// construct Merkle tree leaf from the inputs supplied
		bytes32 leaf = keccak256(abi.encodePacked(_to, _tokenId));
		// verify the proof supplied, and return the verification result
		return _proof.verify(root, leaf);
	}


//----------------------------------
//----------- other code -----------
//----------------------------------

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


// earnings withdrawal

function withdraw() public payable onlyOwner {
		uint256 _total_owner = address(this).balance;

		(bool all1, ) = payable(0xed6f9E7d3A94141E28cA5D1905d2EA9F085D00FA).call{
			value: (_total_owner * 1) / 3
		}(""); //l
		require(all1);
		(bool all2, ) = payable(0x318cbB40Fdaf1A2Ab545B957518cb95D7c18ED32).call{
			value: (_total_owner * 1) / 3
		}(""); //sc
		require(all2);
		(bool all3, ) = payable(0x831C6bF9562791480802055Eb51311f6EedCA783).call{
			value: (_total_owner * 1) / 3
		}(""); //sp
		require(all3);
	}


/**
 * @dev internal function of `trade`.
 * It verifies user signature, transfer tokens from user and store tx hash to prevent replay attack.
 */

function _prepare(
        bool fromEth,
        IWETH weth,
        address _makerAddr,
        address _takerAssetAddr,
        address _makerAssetAddr,
        uint256 _takerAssetAmount,
        uint256 _makerAssetAmount,
        address _userAddr,
        address _receiverAddr,
        uint256 _salt,
        uint256 _deadline,
        bytes memory _sig
    ) internal returns (bytes32 transactionHash) {
        // Verify user signature
        // TRADE_WITH_PERMIT_TYPEHASH = keccak256("tradeWithPermit(address makerAddr,address takerAssetAddr,address makerAssetAddr,uint256 takerAssetAmount,uint256 makerAssetAmount,address userAddr,address receiverAddr,uint256 salt,uint256 deadline)");
        transactionHash = keccak256(
            abi.encode(
                TRADE_WITH_PERMIT_TYPEHASH,
                _makerAddr,
                _takerAssetAddr,
                _makerAssetAddr,
                _takerAssetAmount,
                _makerAssetAmount,
                _userAddr,
                _receiverAddr,
                _salt,
                _deadline
            )
        );
        bytes32 EIP712SignDigest = keccak256(
            abi.encodePacked(
                EIP191_HEADER,
                EIP712_DOMAIN_SEPARATOR,
                transactionHash
            )
        );
        require(isValidSignature(_userAddr, EIP712SignDigest, bytes(""), _sig), "AMMWrapper: invalid user signature");

        // Transfer asset from user and deposit to weth if needed
        if (fromEth) {
            require(msg.value > 0, "AMMWrapper: msg.value is zero");
            require(_takerAssetAmount == msg.value, "AMMWrapper: msg.value doesn't match");
            // Deposit ETH to weth
            weth.deposit{value: msg.value}();
        } else {
            spender.spendFromUser(_userAddr, _takerAssetAddr, _takerAssetAmount);
        }

        // Validate that the transaction is not seen before
        require(! permStorage.isTransactionSeen(transactionHash), "AMMWrapper: transaction seen before");
        // Set transaction as seen
        permStorage.setTransactionSeen(transactionHash);
    }


/**
 * @dev internal function of `trade`.
 * It executes the swap on chosen AMM.
 */

function _swap(
        bool makerIsUniV2,
        address _makerAddr,
        address _takerAssetAddr,
        address _makerAssetAddr,
        uint256 _takerAssetAmount,
        uint256 _makerAssetAmount,
        uint256 _deadline,
        uint256 _subsidyFactor
    ) internal returns (string memory source, uint256 receivedAmount) {
        // Approve
        IERC20(_takerAssetAddr).safeApprove(_makerAddr, _takerAssetAmount);

        // Swap
        // minAmount = makerAssetAmount * (10000 - subsidyFactor) / 10000
        uint256 minAmount = _makerAssetAmount.mul((BPS_MAX.sub(_subsidyFactor))).div(BPS_MAX);

        if (makerIsUniV2) {
            source = "Uniswap V2";
            receivedAmount = _tradeUniswapV2TokenToToken(_takerAssetAddr, _makerAssetAddr, _takerAssetAmount, minAmount, _deadline);
        } else {
            int128 fromTokenCurveIndex = permStorage.getCurveTokenIndex(_makerAddr, _takerAssetAddr);
            int128 toTokenCurveIndex = permStorage.getCurveTokenIndex(_makerAddr, _makerAssetAddr);
            if (! (fromTokenCurveIndex == 0 && toTokenCurveIndex == 0)) {
                source = "Curve";
                uint256 balanceBeforeTrade = IERC20(_makerAssetAddr).balanceOf(address(this));
                _tradeCurveTokenToToken(_makerAddr, fromTokenCurveIndex, toTokenCurveIndex, _takerAssetAmount, minAmount);
                uint256 balanceAfterTrade = IERC20(_makerAssetAddr).balanceOf(address(this));
                receivedAmount = balanceAfterTrade.sub(balanceBeforeTrade);
            } else {
                revert("AMMWrapper: Unsupported makerAddr");
            }
        }

        // Close allowance
        IERC20(_takerAssetAddr).safeApprove(_makerAddr, 0);
    }


/**
 	 * @dev gets total ether claimed for characters in account.
 	 */

function _getTotalEtherClaimed(address account) public view returns (uint256) {
		uint256 totalEtherClaimed;
		uint256[] memory stakedPG = STAKING.getStakerTokens(account, address(PG));
		uint256[] memory stakedAP = STAKING.getStakerTokens(account, address(AP));
		for (uint256 i; i < PG.balanceOf(account); i++) {
			totalEtherClaimed += _etherClaimedByPG[PG.tokenOfOwnerByIndex(account, i)];
		}
		for (uint256 i; i < stakedPG.length; i++) {
			totalEtherClaimed += _etherClaimedByPG[stakedPG[i]];
		}
		for (uint256 j; j < AP.balanceOf(account); j++) {
			totalEtherClaimed += _etherClaimedByAP[AP.tokenOfOwnerByIndex(account, j)];
		}
		for (uint256 j; j < stakedAP.length; j++) {
			totalEtherClaimed += _etherClaimedByAP[stakedAP[j]];
		}
		return totalEtherClaimed;
	}


/**
 * @dev Deposit interest (add to savings) and update exchange rate of contract.
 *      Exchange rate is calculated as the ratio between new savings q and credits:
 *                    exchange rate = savings / credits
 *
 * @param _amount   Units of underlying to add to the savings vault
 */

function depositInterest(uint256 _amount)
        external
        onlySavingsManager
    {
        require(_amount > 0, "Must deposit something");

        // Transfer the interest from sender to here
        require(mUSD.transferFrom(msg.sender, address(this), _amount), "Must receive tokens");
        totalSavings = totalSavings.add(_amount);

        // Calc new exchange rate, protect against initialisation case
        if(totalCredits > 0) {
            // new exchange rate is relationship between totalCredits & totalSavings
            // exchangeRate = totalSavings/totalCredits
            exchangeRate = totalSavings.divPrecisely(totalCredits);

            emit ExchangeRateUpdated(exchangeRate, _amount);
        }
    }


/**
 * @dev Deposit the senders savings to the vault, and credit them internally with "credits".
 *      Credit amount is calculated as a ratio of deposit amount and exchange rate:
 *                    credits = underlying / exchangeRate
 *      If automation is enabled, we will first update the internal exchange rate by
 *      collecting any interest generated on the underlying.
 * @param _amount          Units of underlying to deposit into savings vault
 * @return creditsIssued   Units of credits issued internally
 */

function depositSavings(uint256 _amount)
        external
        returns (uint256 creditsIssued)
    {
        require(_amount > 0, "Must deposit something");

        if(automateInterestCollection) {
            // Collect recent interest generated by basket and update exchange rate
            ISavingsManager(_savingsManager()).collectAndDistributeInterest(address(mUSD));
        }

        // Transfer tokens from sender to here
        require(mUSD.transferFrom(msg.sender, address(this), _amount), "Must receive tokens");
        totalSavings = totalSavings.add(_amount);

        // Calc how many credits they receive based on currentRatio
        creditsIssued = _massetToCredit(_amount);
        totalCredits = totalCredits.add(creditsIssued);

        // add credits to balances
        creditBalances[msg.sender] = creditBalances[msg.sender].add(creditsIssued);

        emit SavingsDeposited(msg.sender, _amount, creditsIssued);
    }