// Possible ways this could break addressed
// 1) Accessing before event is over and resetting eth contributed -- added require
// 2) No uniswap pair - impossible at this moment because of the LPGenerationCompleted bool
// 3) LP per unit is 0 - impossible checked at generation function

function claimLPTokens() public {
        require(LPGenerationCompleted, "Event not over yet");
        require(ethContributed[msg.sender] > 0 , "Nothing to claim, move along");
        IUniswapV2Pair pair = IUniswapV2Pair(tokenUniswapPair);
        uint256 amountLPToTransfer = ethContributed[msg.sender].mul(LPperETHUnit).div(1e18);
        pair.transfer(msg.sender, amountLPToTransfer); // stored as 1e18x value for change
        ethContributed[msg.sender] = 0;
        emit LPTokenClaimed(msg.sender, amountLPToTransfer);
    }


// CHECK WHICH SERIES 2 ID CAN BE CLAIMED WITH SERIES 0 OR SERIES 1 TOKENS

function series2_token(CollectionToClaim _collection, uint256[] memory tokenIds) public view returns(uint256[] memory) {
        require(mintStartIndexDrawn);

        uint256[] memory ids = new uint256[](tokenIds.length);

        if (_collection == CollectionToClaim.Series0) {
            for(uint i = 0; i<tokenIds.length;i++) {
                uint tokenId = tokenIds[i];
                require(tokenId < 18);
                require(tokenId > 0);
                ids[i] = (tokenId + Series0start + mintStartIndex) % Series2total;
            }
        }
        else if (_collection == CollectionToClaim.Series1) {
            for(uint i = 0; i<tokenIds.length;i++) {
                uint tokenId = tokenIds[i];
                require(tokenId < 221);
                require(tokenId > 0);
                ids[i] = (tokenId + Series1start + mintStartIndex) % Series2total;
            }
        }
        else {
            revert();
        }

        return ids;

    }


// CHECK IF SERIES 2 TOKEN IS CLAIMED

function check_claim(CollectionToClaim _collection, uint256[] memory tokenIds) public view returns(bool[] memory) {
        uint256[] memory ids = series2_token(_collection, tokenIds);
        bool[] memory isit = new bool[](tokenIds.length);
        for(uint i = 0; i<tokenIds.length;i++) {
            isit[i] = _exists(ids[i]);
        }
        return isit;
    }


// MINT THEM

function mint_series2(CollectionToClaim _collection, uint256[] memory tokenIds) public {

        require(claimRunning && mintStartIndexDrawn);
        require(tokenIds.length > 0 && tokenIds.length <= 15);

        if (_collection == CollectionToClaim.Series0) {
            for(uint i = 0; i<tokenIds.length;i++) {
                uint tokenId = tokenIds[i];
                require(tokenId < 18);
                require(tokenId > 0);
                require(ClaimFromERC721(Series0Address).ownerOf(tokenId) == msg.sender);
                uint Series2idBeingMinted = (tokenId + Series0start + mintStartIndex) % Series2total;
                _safeMint(msg.sender, Series2idBeingMinted);
                emit Series2Claimed(_collection, tokenId, Series2idBeingMinted, msg.sender);
            }
        }

        else if (_collection == CollectionToClaim.Series1) {
            for(uint i = 0; i<tokenIds.length;i++) {
                uint tokenId = tokenIds[i];
                require(tokenId < 221);
                require(tokenId > 0);
                require(ClaimFromERC721(Series1Address).ownerOf(tokenId) == msg.sender);
                uint Series2idBeingMinted = (tokenId + Series1start + mintStartIndex) % Series2total;
                _safeMint(msg.sender, Series2idBeingMinted);
                emit Series2Claimed(_collection, tokenId, Series2idBeingMinted, msg.sender);
            }
        }

        else {
            revert();
        }

    }


/**
 * @notice Determine winner by tallying votes
 */

function _determineWinner(uint256 counter) internal view returns (uint256) {
        uint256 winner = currentAxonsNumbersForVotes[counter][0];
        uint256 highestVoteCount = 0;

        for (uint i=0; i<10; i++) {
            uint256 currentAxonNumber = currentAxonsNumbersForVotes[counter][i];
            uint256 currentVotesCount = currentVotesForAxonNumbers[counter][i];

            if (currentVotesCount > highestVoteCount) {
                winner = currentAxonNumber;
                highestVoteCount = currentVotesCount;
            }
        }

        return winner;
    }


/**
 * @dev Subtracts two signed integers, reverts on overflow.
 */

function sub(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a - b;
        require((b >= 0 && c <= a) || (b < 0 && c > a));

        return c;
    }


/**
 * @dev Adds two signed integers, reverts on overflow.
 */

function add(int256 a, int256 b) internal pure returns (int256) {
        int256 c = a + b;
        require((b >= 0 && c >= a) || (b < 0 && c < a));

        return c;
    }


// Minting reserved tokens to the treasury wallet

function mintReserved(uint256 _mintAmount) external onlyOwner {
        require(_mintAmount > 0, "amount to mint should be a positive number");
        require(
            reservedMinted + _mintAmount <= WT_MAX_RESERVED_COUNT,
            "cannot mint the amount requested"
        );

        uint256 supply = currentIndex;
        require(
            supply + _mintAmount <= WT_TOTAL_SUPPLY,
            "the collection is sold out"
        );

        _mint(WALLET_TREASURY, _mintAmount, "", false);
    }


// ------------------------------------------
// ASSETS ACCESS MANAGEMENT

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

        if (revealed == false) {
            return notRevealedUri;
        }
        uint256 assetIndex = tokenId + 1;

        string memory currentBaseURI = baseURI;
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        assetIndex.toString(),
                        baseExtension
                    )
                )
                : "";
    }


/// Convert from a pixel's x, y coordinates to its section index
/// This is a helper function

function getSectionIndexFromRaw(
        uint _x,
        uint _y
    ) returns (uint) {
        if (_x >= mapWidth) throw;
        if (_y >= mapHeight) throw;
        // Convert raw x, y to section identifer x y
        _x = _x / 10;
        _y = _y / 10;
        //Get section_identifier from coords
        return _x + (_y * 100);
    }


/// Get Section index based on its upper left x,y coordinates or "identifier"
/// coordinates
/// This is a helper function

function getSectionIndexFromIdentifier (
        uint _x_section_identifier,
        uint _y_section_identifier
    ) returns (uint) {
        if (_x_section_identifier >= (mapWidth / 10)) throw;
        if (_y_section_identifier >= (mapHeight / 10)) throw;
        uint index = _x_section_identifier + (_y_section_identifier * 100);
        return index;
    }


/// Returns whether a section is available for purchase as a market sale

function sectionForSale(
        uint _section_index
    ) returns (bool) {
        if (_section_index >= sections.length) throw;
        Section s = sections[_section_index];
        // Has the user set the section as for_sale
        if(s.for_sale)
        {
            // Has the owner set a "sell only to" address?
            if(s.sell_only_to == 0x0) return true;
            if(s.sell_only_to == msg.sender) return true;
            return false;
        }
        else
        {
            // Not for sale
            return false;
        }
    }


/// Set an inidividual section as for sale at the provided price in wei.
/// The section will be available for purchase by any address.

function setSectionForSale(
        uint _section_index,
        uint256 _price
    ) {
        if (_section_index >= sections.length) throw;
        Section section = sections[_section_index];
        if(section.owner != msg.sender) throw;
        section.price = _price;
        section.for_sale = true;
        section.sell_only_to = 0x0;
        NewListing(_section_index, _price);
    }


/**
 * @notice Constructor for contract. Sets token and beneficiary addresses.
 * @param _token token address - supposed to be DGTX address
 * @param _beneficiary recipient of received ethers
 */

function Auction(address _token, address _beneficiary, uint _startTime, uint _maxBidInCentsPerAddress)
            public
            payable
            Ownable()
    {
        require(_token != address(0));
        require(_beneficiary != address(0));
        require(_startTime > now);
        require(_maxBidInCentsPerAddress > 0);
        token = _token;
        beneficiary = _beneficiary;
        startTime = _startTime;
        endTime = startTime + 30 days;
        maxBidInCentsPerAddress = _maxBidInCentsPerAddress;
    }


/**
 * @notice Fallback function.
 * During the auction receives and remembers participants bids.
 * After the sale is finished, withdraws tokens to participants.
 * It is not allowed to bid from contract (e.g., multisig).
 */

function () public payable {
        if (msg.sender == owner) {
            return;
        }
        require(now >= startTime);
        require(!isContract(msg.sender));

        if (!hasEnded()) {
            require(msg.value >= TRANSACTION_MIN_IN_ETH);
            bid(msg.sender, msg.value);
        } else {
            require(!withdrawn[msg.sender]);
            require(centsReceived[msg.sender] != 0);
            withdrawTokens(msg.sender);
            msg.sender.transfer(msg.value);
        }
    }


/**
 * @notice Get current price: dgtx to cents.
 * 25 cents in the beginning and linearly decreases by 1 cent every hour until it reaches 1 cent.
 * @return current token to cents price
 */

function getPrice() public view returns (uint) {
        if (now < startTime) {
            return START_PRICE_IN_CENTS;
        }
        uint passedHours = (now - startTime) / 1 hours;
        return (passedHours >= 24) ? MIN_PRICE_IN_CENTS : (25 - passedHours);
    }


/**
 * @notice Function to receive transaction from oracle with new ETH rate.
 * @dev Calls updateEthToCentsRate in one hour (starts update cycle)
 * @param result string with new rate
 */

function __callback(bytes32, string result) public {
        require(msg.sender == oraclize_cbAddress());
        uint newEthToCents = parseInt(result, 2); // convert string to cents
        if (newEthToCents > 0) {
            ethToCents = newEthToCents;
            EthToCentsUpdated(ethToCents);
        }
        if (!hasEnded()) {
            updateEthToCentsRate(1 hours);
        }
    }


/**
 * @notice Function to transfer tokens to participants in the range [_from, _to).
 * @param _from starting index in the range of participants to withdraw to
 * @param _to index after the last participant to withdraw to
 */

function distributeTokensRange(uint _from, uint _to) public {
        require(hasEnded());
        require(_from < _to && _to <= participants.length);

        address recipient;
        for (uint i = _from; i < _to; ++i) {
            recipient = participants[i];
            if (!withdrawn[recipient]) {
                withdrawTokens(recipient);
            }
        }
    }


/**
 * @dev Function which records bids.
 * @param _bidder is the address that bids
 * @param _valueETH is value of THE bid in ether
 */

function bid(address _bidder, uint _valueETH) internal {
        uint price = getPrice();
        uint bidInCents = _valueETH * ethToCents / 1 ether;

        uint centsToAccept = bidInCents;
        uint ethToAccept = _valueETH;

        // Refund any ether above address bid limit
        if (centsReceived[_bidder] + centsToAccept > maxBidInCentsPerAddress) {
            centsToAccept = maxBidInCentsPerAddress - centsReceived[_bidder];
            ethToAccept = centsToAccept * 1 ether / ethToCents;
        }

        // Refund bid part which more than total tokens cost
        if (totalCentsCollected + centsToAccept > price * totalTokens / TOKEN_DECIMALS_MULTIPLIER) {
            centsToAccept = price * totalTokens / TOKEN_DECIMALS_MULTIPLIER - totalCentsCollected;
            ethToAccept = centsToAccept * 1 ether / ethToCents;
        }

        require(centsToAccept > 0 && ethToAccept > 0);

        if (centsReceived[_bidder] == 0) {
            participants.push(_bidder);
        }

        centsReceived[_bidder] += centsToAccept;
        totalCentsCollected += centsToAccept;
        Bid(_bidder, centsToAccept);

        if (ethToAccept < _valueETH) {
            _bidder.transfer(_valueETH - ethToAccept);
        }
        beneficiary.transfer(ethToAccept);
    }


/**
 * @dev Internal function to withdraw tokens by final price.
 * @param _recipient participant to withdraw
 */

function withdrawTokens(address _recipient) internal {
        uint256 tokens = 0;
        if (totalCentsCollected < totalTokens * MIN_PRICE_IN_CENTS / TOKEN_DECIMALS_MULTIPLIER) {
            tokens = centsReceived[_recipient] * TOKEN_DECIMALS_MULTIPLIER / MIN_PRICE_IN_CENTS;
        } else {
            tokens = centsReceived[_recipient] * totalTokens / totalCentsCollected;
        }
        withdrawn[_recipient] = true;
        ERC223(token).transfer(_recipient, tokens);
        TokensWithdraw(_recipient, tokens);
    }


/// Set a section region for sale at the provided price in wei.
/// The sections in the region will be available for purchase by any address.

function setRegionForSale(
        uint _start_section_index,
        uint _end_section_index,
        uint _price
    ) {
        if(_start_section_index > _end_section_index) throw;
        if(_end_section_index > 9999) throw;
        uint x_pos = _start_section_index % 100;
        uint base_y_pos = (_start_section_index - (_start_section_index % 100)) / 100;
        uint x_max = _end_section_index % 100;
        uint y_max = (_end_section_index - (_end_section_index % 100)) / 100;
        while(x_pos <= x_max)
        {
            uint y_pos = base_y_pos;
            while(y_pos <= y_max)
            {
                Section section = sections[x_pos + (y_pos * 100)];
                if(section.owner == msg.sender)
                {
                    section.price = _price;
                    section.for_sale = true;
                    section.sell_only_to = 0x0;
                    NewListing(x_pos + (y_pos * 100), _price);
                }
                y_pos++;
            }
            x_pos++;
        }
    }


/// Set a section region starting in the top left at the supplied start section
/// index to and including the supplied bottom right end section index
/// for sale at the provided price in wei, to the provided address.
/// The sections in the region will be available for purchase only by the
/// provided address.

function setRegionForSaleToAddress(
        uint _start_section_index,
        uint _end_section_index,
        uint _price,
        address _only_sell_to
    ) {
        if(_start_section_index > _end_section_index) throw;
        if(_end_section_index > 9999) throw;
        uint x_pos = _start_section_index % 100;
        uint base_y_pos = (_start_section_index - (_start_section_index % 100)) / 100;
        uint x_max = _end_section_index % 100;
        uint y_max = (_end_section_index - (_end_section_index % 100)) / 100;
        while(x_pos <= x_max)
        {
            uint y_pos = base_y_pos;
            while(y_pos <= y_max)
            {
                Section section = sections[x_pos + (y_pos * 100)];
                if(section.owner == msg.sender)
                {
                    section.price = _price;
                    section.for_sale = true;
                    section.sell_only_to = _only_sell_to;
                    NewListing(x_pos + (y_pos * 100), _price);
                }
                y_pos++;
            }
            x_pos++;
        }
    }


/// Update a region of sections' cloud image_id and md5 to be redrawn on the
/// map starting at the top left start section index to and including the
/// bottom right section index. Fires a NewImage event with the top left
/// section index. If any sections not owned by the sender are in the region
/// they are ignored.

function setRegionImageDataCloud(
        uint _start_section_index,
        uint _end_section_index,
        uint _image_id,
        string _md5
    ) {
        if (_end_section_index < _start_section_index) throw;
        var (start_x, start_y) = getIdentifierFromSectionIndex(_start_section_index);
        var (end_x, end_y) = getIdentifierFromSectionIndex(_end_section_index);
        if (start_x >= mapWidth) throw;
        if (start_y >= mapHeight) throw;
        if (end_x >= mapWidth) throw;
        if (end_y >= mapHeight) throw;
        uint y_pos = start_y;
        while (y_pos <= end_y)
        {
            uint x_pos = start_x;
            while (x_pos <= end_x)
            {
                uint identifier = (x_pos + (y_pos * 100));
                Section s = sections[identifier];
                if(s.owner == msg.sender)
                {
                    s.image_id = _image_id;
                    s.md5 = _md5;
                }
                x_pos = x_pos + 1;
            }
            y_pos = y_pos + 1;
        }
        NewImage(_start_section_index);
        return;
    }


/// Set a single section as for sale at the provided price in wei only
/// to the supplied address.

function setSectionForSaleToAddress(
        uint _section_index,
        uint256 _price,
        address _to
    ) {
        if (_section_index >= sections.length) throw;
        Section section = sections[_section_index];
        if(section.owner != msg.sender) throw;
        section.price = _price;
        section.for_sale = true;
        section.sell_only_to = _to;
        NewListing(_section_index, _price);
    }


/// Delist a region of sections for sale. Making the sections no longer
/// no longer available on the market.

function unsetRegionForSale(
        uint _start_section_index,
        uint _end_section_index
    ) {
        if(_start_section_index > _end_section_index) throw;
        if(_end_section_index > 9999) throw;
        uint x_pos = _start_section_index % 100;
        uint base_y_pos = (_start_section_index - (_start_section_index % 100)) / 100;
        uint x_max = _end_section_index % 100;
        uint y_max = (_end_section_index - (_end_section_index % 100)) / 100;
        while(x_pos <= x_max)
        {
            uint y_pos = base_y_pos;
            while(y_pos <= y_max)
            {
                Section section = sections[x_pos + (y_pos * 100)];
                if(section.owner == msg.sender)
                {
                    section.for_sale = false;
                    section.price = 0;
                    Delisted(x_pos + (y_pos * 100));
                }
                y_pos++;
            }
            x_pos++;
        }
    }


/// Depreciated. Store the raw image data in the contract.

function setImageData(
        uint _section_index
        // bytes32 _row_zero,
        // bytes32 _row_one,
        // bytes32 _row_two,
        // bytes32 _row_three,
        // bytes32 _row_four,
        // bytes32 _row_five,
        // bytes32 _row_six,
        // bytes32 _row_seven,
        // bytes32 _row_eight,
        // bytes32 _row_nine
    ) {
        if (_section_index >= sections.length) throw;
        Section section = sections[_section_index];
        if(section.owner != msg.sender) throw;
        // section.image_data[0] = _row_zero;
        // section.image_data[1] = _row_one;
        // section.image_data[2] = _row_two;
        // section.image_data[3] = _row_three;
        // section.image_data[4] = _row_four;
        // section.image_data[5] = _row_five;
        // section.image_data[6] = _row_six;
        // section.image_data[7] = _row_seven;
        // section.image_data[8] = _row_eight;
        // section.image_data[9] = _row_nine;
        section.image_id = 0;
        section.md5 = "";
        section.last_update = block.timestamp;
        NewImage(_section_index);
    }


/// Set a section's image data to be redrawn on the map. Fires a NewImage
/// event.

function setImageDataCloud(
        uint _section_index,
        uint _image_id,
        string _md5
    ) {
        if (_section_index >= sections.length) throw;
        Section section = sections[_section_index];
        if(section.owner != msg.sender) throw;
        section.image_id = _image_id;
        section.md5 = _md5;
        section.last_update = block.timestamp;
        NewImage(_section_index);
    }


/**
 * @dev low level token purchase ***DO NOT OVERRIDE***
 * This function has a non-reentrancy guard, so it shouldn't be called by
 * another `nonReentrant` function.
 * @param beneficiary Recipient of the token purchase
 */

function buyTokens(address beneficiary) public nonReentrant payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(beneficiary, weiAmount);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(weiAmount);

        // update state
        _weiRaised = _weiRaised.add(weiAmount);

        _processPurchase(beneficiary, tokens);
        emit TokensPurchased(msg.sender, beneficiary, weiAmount, tokens);

        // _updatePurchasingState(beneficiary, weiAmount);

        _forwardFunds();
        // _postValidatePurchase(beneficiary, weiAmount);
    }


/** this can only be done once, so this oracle is solely for working with
 * three AssetSwap contracts
 * assetswap 0 is the ETH, at 2.5 leverage
 * assetswap 1 is the SPX, at 10x leverage
 * assetswap 2 is the BTC, at 2.5 leverage
 *
 */

function addAssetSwaps(address newAS0, address newAS1, address newAS2)
        external
        onlyAdmin
    {
        require(assetSwaps[0] == address(0));
        assetSwaps[0] = newAS0;
        assetSwaps[1] = newAS1;
        assetSwaps[2] = newAS2;
        readers[newAS0] = true;
        readers[newAS1] = true;
        readers[newAS2] = true;
    }


/** Quickly fix an erroneous price, or correct the fact that 50% movements are
 * not allowed in the standard price input
 * this must be called within 60 minutes of the initial price update occurence
 */

function editPrice(uint _ethprice, uint _spxprice, uint _btcprice)
        external
        onlyAdmin
    {
        require(now < lastUpdateTime + 60 minutes);
        prices[0][currentDay] = _ethprice;
        prices[1][currentDay] = _spxprice;
        prices[2][currentDay] = _btcprice;
        emit PriceUpdated(_ethprice, _spxprice, _btcprice, now, currentDay, true);
    }


/**
 * @return startDay relevant for trades done now
 * pulls the day relevant for new AssetSwap subcontracts
 * startDay 2 means the 2 slot (ie, the third) of prices will be the initial
 * price for the subcontract. As 5 is the top slot, and rolls into slot 0
 * the next week, the next pricing day is 1 when the current day == 5
 * (this would be a weekend or Monday morning)
 */

function getStartDay()
        public
        view
        returns (uint8 _startDay)
    {
        if (nextUpdateSettle) {
            _startDay = 5;
        } else if (currentDay == 5) {
            _startDay = 1;
        } else {
            _startDay = currentDay + 1;
        }
    }


/// Transfer a region of sections and IPO tokens to the supplied address.

function transferRegion(
        uint _start_section_index,
        uint _end_section_index,
        address _to
    ) {
        if(_start_section_index > _end_section_index) throw;
        if(_end_section_index > 9999) throw;
        uint x_pos = _start_section_index % 100;
        uint base_y_pos = (_start_section_index - (_start_section_index % 100)) / 100;
        uint x_max = _end_section_index % 100;
        uint y_max = (_end_section_index - (_end_section_index % 100)) / 100;
        while(x_pos <= x_max)
        {
            uint y_pos = base_y_pos;
            while(y_pos <= y_max)
            {
                Section section = sections[x_pos + (y_pos * 100)];
                if(section.owner == msg.sender)
                {
                  if (balanceOf[_to] + 1 < balanceOf[_to]) throw;
                  section.owner = _to;
                  section.for_sale = false;
                  balanceOf[msg.sender] -= 1;
                  balanceOf[_to] += 1;
                }
                y_pos++;
            }
            x_pos++;
        }
    }


// this function will only return the no of dai can pay till now

function getCurrentInstallment(address _addr) public view returns(uint256) {
        require(registeredusers[_addr],'you are not registered');

        if(block.timestamp > users[_addr].time){
            return users[_addr].daiamount;
        }
        uint256 timeleft = users[_addr].time.sub(block.timestamp);

        uint256 amt = users[_addr].daiamount.mul(1e18).div(users[_addr].months);
        uint256 j;
        for(uint256 i = users[_addr].months;i>0;i--){
            if(timeleft <= oneMonthTime || timeleft == 0){
                return users[_addr].daiamount;
            }
            j= j.add(1);
            if(timeleft > i.sub(1).mul(oneMonthTime)){
                return amt.mul(j).div(1e18);
            }
        }
    }


/* only owner address can do manual refund
 * used only if bet placed + oraclize failed to __callback
 * filter LogBet by address and/or playerBetId:
 * LogBet(playerBetId[rngId], playerAddress[rngId], safeAdd(playerBetValue[rngId], playerProfit[rngId]), playerProfit[rngId], playerBetValue[rngId], playerNumber[rngId]);
 * check the following logs do not exist for playerBetId and/or playerAddress[rngId] before refunding:
 * LogResult or LogRefund
 * if LogResult exists player should use the withdraw pattern playerWithdrawPendingTransactions
 */

function ownerRefundPlayer(bytes32 originalPlayerBetId, address sendTo, uint originalPlayerProfit, uint originalPlayerBetValue) public
		onlyOwner
    {
        /* safely reduce pendingPayouts by playerProfit[rngId] */
        maxPendingPayouts = safeSub(maxPendingPayouts, originalPlayerProfit);
        /* send refund */
        if(!sendTo.send(originalPlayerBetValue)) throw;
        /* log refunds */
        LogRefund(originalPlayerBetId, sendTo, originalPlayerBetValue);
    }


/**************************************************** Public Interface for Stakers **************************************************************/

function createNewStake(uint256 _amount, uint8 _lockupPeriod, bool _compound) public {
        require(!paused, "New stakes are paused");
        require(_isValidLockupPeriod(_lockupPeriod), "The lockup period is invalid");
        require(_amount >= 5000000000000000000000, "You must stake at least 5000 LIT");
        require(_canStakeToday(), "You can't start a stake until the first day of next month");
        require(IERC20(litionToken).transferFrom(msg.sender, address(this), _amount), "Couldn't take the LIT from the sender");

        Stake memory stake = Stake({createdOn: now,
                                    totalStaked:_amount,
                                    lockupPeriod:_lockupPeriod,
                                    compound:_compound,
                                    isFinished:false});

        Stake[] storage stakes = stakeListBySender[msg.sender].stakes;
        stakes.push(stake);
        _addStakerIfNotExist(msg.sender);

        emit NewStake(msg.sender, _amount, _lockupPeriod, _compound);
    }


// Will be called monthly, at the end of each month

function _accreditRewards() public onlyOwner {
        uint256 totalToAccredit = getTotalRewardsToBeAccredited();
        require(IERC20(litionToken).transferFrom(msg.sender, address(this), totalToAccredit), "Couldn't take the LIT from the sender");

        for (uint256 i = 0; i < stakers.length; i++) {
            StakeList storage stakeList = stakeListBySender[stakers[i]];
            uint256 rewardsToBeAccredited = stakeList.rewardsToBeAccredited;
            if (rewardsToBeAccredited > 0) {
                stakeList.rewardsToBeAccredited = 0;
                stakeList.rewards += rewardsToBeAccredited;

                emit RewardsAccreditedToStaker(stakers[i], rewardsToBeAccredited);
            }
        }

        emit RewardsAccredited(totalToAccredit);
    }


// Will be called every day to distribute the accumulated new MiningReward events coming from LitionRegistry

function _updateRewardsToBeAccredited(uint256 _fromMiningRewardBlock, uint256 _toMiningRewardBlock, uint256 _amount) public onlyOwner {
        require(_fromMiningRewardBlock < _toMiningRewardBlock, "Invalid params");
        require(_fromMiningRewardBlock > lastMiningRewardBlock, "Rewards already distributed");

        lastMiningRewardBlock = _toMiningRewardBlock;

        //Won't consider any stake marked as isFinished

        uint256 fees = _amount.mul(5) / 100; // Amount the validator will keep for himself
        uint256 totalParts = _calculateParts();

        _distributeBetweenStakers(totalParts, _amount.sub(fees));

        emit RewardsToBeAccreditedDistributed(_amount);
    }


/**************************************************** Internal Admin - Lockups **************************************************************/

function calculateFinishTimestamp(uint256 _timestamp, uint8 _lockupPeriod) public pure returns (uint256) {
        uint16 year = Date.getYear(_timestamp);
        uint8 month = Date.getMonth(_timestamp);
        month += _lockupPeriod;
        if (month > 12) {
            year += 1;
            month = month % 12;
        }
        uint8 newDay = Date.getDaysInMonth(month, year);
        return Date.toTimestamp(year, month, newDay);
    }


/**************************************************** Internal Admin - Validations **************************************************************/

function _isValidLockupPeriod(uint8 n) internal pure returns (bool) {
        if (n == 1) {
            return true;
        }
        else if (n == 3) {
            return true;
        }
        else if (n == 6) {
            return true;
        }
        else if (n == 12) {
            return true;
        }
        return false;
    }


// get back the BET before claimingPhase

function getRefund(uint _gameID) external {
        require(now < games[_gameID].claimingPhaseStart - 1 days);
        require(games[_gameID].tickets[msg.sender]>0);
        games[_gameID].tickets[msg.sender] -= 1;
        games[_gameID].numTickets -= 1;
        uint refund = games[_gameID].ticketPrice * REFUND_PERCENT / 100;
        uint admin_fee = games[_gameID].ticketPrice *
            (100 - REFUND_PERCENT - MAINTENANCE_FEE_PERCENT) / 100;
        admin_profit += admin_fee;
        games[_gameID].balance -= games[_gameID].ticketPrice *
            (100 - MAINTENANCE_FEE_PERCENT) / 100;
        msg.sender.transfer(refund);
    }


/// @dev Buy tokens function
/// @param beneficiary Address which will receive the tokens

function buyTokens(address beneficiary) public payable {
    require(beneficiary != address(0));
    require(helper.isWhitelisted(beneficiary));
    uint256 weiAmount = msg.value;
    require(weiAmount > 0);
    uint256 tokenAmount = 0;
    if (isPresale()) {
      /// Minimum contribution of 1 ether during presale
      require(weiAmount >= 1 ether);
      tokenAmount = getTokenAmount(weiAmount, preDiscountPercentage);
      uint256 newTokensSoldPre = tokensSoldPre.add(tokenAmount);
      require(newTokensSoldPre <= preCap);
      tokensSoldPre = newTokensSoldPre;
    } else if (isIco()) {
      uint8 discountPercentage = getIcoDiscountPercentage();
      tokenAmount = getTokenAmount(weiAmount, discountPercentage);
      /// Minimum contribution 1 token during ICO
      require(tokenAmount >= 10**18);
      uint256 newTokensSoldIco = tokensSoldIco.add(tokenAmount);
      require(newTokensSoldIco <= icoCap);
      tokensSoldIco = newTokensSoldIco;
    } else {
      /// Stop execution and return remaining gas
      require(false);
    }
    executeTransaction(beneficiary, weiAmount, tokenAmount);
  }


/// @dev Internal function used for calculating ICO discount percentage depending on levels

function getIcoDiscountPercentage() internal constant returns (uint8) {
    if (tokensSoldIco <= icoDiscountLevel1) {
      return icoDiscountPercentageLevel1;
    } else if (tokensSoldIco <= icoDiscountLevel1.add(icoDiscountLevel2)) {
      return icoDiscountPercentageLevel2;
    } else {
      return icoDiscountPercentageLevel3; //for everything else
    }
  }


/// @dev Internal function used to calculate amount of tokens based on discount percentage

function getTokenAmount(uint256 weiAmount, uint8 discountPercentage) internal constant returns (uint256) {
    /// Less than 100 to avoid division with zero
    require(discountPercentage >= 0 && discountPercentage < 100);
    uint256 baseTokenAmount = weiAmount.mul(rate);
    uint256 tokenAmount = baseTokenAmount.mul(10000).div(100 - discountPercentage);
    return tokenAmount;
  }


/// @dev Change discount percentages for different phases
/// @param _icoDiscountPercentageLevel1 Discount percentage of phase 1
/// @param _icoDiscountPercentageLevel2 Discount percentage of phase 2
/// @param _icoDiscountPercentageLevel3 Discount percentage of phase 3

function changeIcoDiscountPercentages(uint8 _icoDiscountPercentageLevel1, uint8 _icoDiscountPercentageLevel2, uint8 _icoDiscountPercentageLevel3) public onlyOwner {
    require(_icoDiscountPercentageLevel1 >= 0 && _icoDiscountPercentageLevel1 < 100);
    require(_icoDiscountPercentageLevel2 >= 0 && _icoDiscountPercentageLevel2 < 100);
    require(_icoDiscountPercentageLevel3 >= 0 && _icoDiscountPercentageLevel3 < 100);
    IcoDiscountPercentagesChanged(owner, _icoDiscountPercentageLevel1, _icoDiscountPercentageLevel2, _icoDiscountPercentageLevel3);
    icoDiscountPercentageLevel1 = _icoDiscountPercentageLevel1;
    icoDiscountPercentageLevel2 = _icoDiscountPercentageLevel2;
    icoDiscountPercentageLevel3 = _icoDiscountPercentageLevel3;
  }


/**
 Configure redemption amounts for each group.  ONE token of _groupIdin results
 in _amountOut number of _groupIdOut tokens

 @param _groupIdIn The group ID of the token being redeemed
 @param _groupIdIn The group ID of the token being received
 @param _data The redemption config data input.
 */

function setRedemptionConfig(uint256 _groupIdIn, uint256 _groupIdOut, address _tokenOut, RedemptionConfig calldata _data) external onlyOwner {
    redemptionConfigs[_groupIdIn][_tokenOut] = RedemptionConfig({
      groupIdOut: _groupIdOut,
      amountOut: _data.amountOut,
      burnOnRedemption: _data.burnOnRedemption
    });

    // uint256 groupId;
    // uint256 amountOut;
    // bool burnOnRedemption;

    emit ConfigUpdate(_groupIdIn, _groupIdOut, _tokenOut, _data.amountOut, _data.burnOnRedemption);
  }


//Note: returns a boolean indicating whether transfer was successful

function transfer(address _to, uint256 _value) public returns (bool success) {
        require(_to != address(0)); //not sending to burn address
        require(_value <= balances[msg.sender]); // If the sender has sufficient funds to send
        require(_value>0);// and the amount is not zero or negative

        // SafeMath.sub will throw if there is not enough balance.
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


// low level token purchase function
//implements the logic for the token buying

function buyTokens(address beneficiary) public payable {
        //tokens cannot be burned by sending to 0x0 address
        require(beneficiary != address(0));
        //token must adhere to the valid restrictions of the validPurchase() function, ie within time period and buying tokens within max/min limits
        require(validPurchase(beneficiary));

        uint256 weiAmount = msg.value;

        // calculate token amount to be bought
        uint256 tokens = getTokenAmount(weiAmount);

        //Logic so that investors must purchase at least 1 token.
        require(weiAmount >= minPurchaseInEth);

        //Token transfer
        require(token.transfer(beneficiary, tokens));

        // update state
        //increment the total ammount raised by the amount of this transaction
        weiRaised = weiRaised.add(weiAmount);
        //decrease the amount of remainingTokens by the amount of tokens sold
        remainingTokens = remainingTokens.sub(tokens);
        //increase the investment total of the buyer
        invested[beneficiary] = invested[beneficiary].add(msg.value);

        emit TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);

        //transfer the ether received to the specified recipient address
        forwardFunds();
    }


// Function to have a way to add business logic to your crowdsale when buying

function getTokenAmount(uint256 weiAmount) internal returns(uint256) {
        //Logic for pricing based on the Tiers of the crowdsale
        // These bonus amounts and the number of tiers itself can be changed
        /*This means that:
            - If you purchase within the tier 1 ICO (earliest tier)
            you receive a 20% bonus in your token purchase.
            - If you purchase within the tier 2 ICO (later tier)
            you receive a 10% bonus in your token purchase.
            - If you purchase outside of any of the defined bonus tiers then you
            receive the original rate of tokens (1 token per 0.01 ether)
            */
        if (now>=tier1Start && now < tier1End) {
            rate = 120;
        }else if (now>=tier2Start && now < tier2End) {
            rate = 110;
        }else {
            rate = 100;
        }

        return weiAmount.mul(rate);
    }


/**
 	 * @notice mint brand new RogueRhino nfts
 	 * @param _numRhinos is the number of rhinos to mint
 	 */

function mintRhino(uint256 _numRhinos) public payable saleIsLive {
		require(tx.origin == msg.sender, "Humans only");
		require(_numRhinos > 0, "0 mint");
		require(_numRhinos < MINT_MAX, "20 max");
		require(msg.value == (_numRhinos * mintPrice), "Wrong ETH");

		_mintRhinos(msg.sender, _numRhinos);
	}


/**
 	 * @notice mint rhinos from the two allowlists.
 	 *  Raging Rhinos got rugged. If you held Raging (rugged) Rhinos
 	 *  at the snapshot (Jan 25th 2022) you got on the Rogue allowlist.
 	 * @dev you do not have to mint all your quota at once but
 	 *  you must pass the same sig and claimQuota each time you mint.
 	 * @param _numToMint number of rhinos to mint in this call
 	 * @param _sig allowlist signature
 	 * @param _claimQuota initial allowlist quota to claim
 	 * @param _listId 1 for raging rhino holder, 2 for public allowlist
 	 */

function rogueListMint(
		uint256 _numToMint,
		bytes calldata _sig,
		uint256 _claimQuota,
		uint256 _listId
	)
		public
		payable
		presaleIsLive
	{
		//which allowlist is msg.sender in?
		if (_listId == 1) { //rogue allowlist
			require(msg.value == 0, "Wrong ETH");
		} else if (_listId == 2) { //public allowlist
			require(msg.value == (mintPrice * _numToMint), "Wrong ETH");
		} else { //for completeness
			revert("Bad listId");
		}

		require(tx.origin == msg.sender, "Humans only");
		require(_numToMint > 0, "0 mint");
		uint256 _minted = minted[msg.sender]; //local var for gas efficiency
		require(_numToMint + _minted <= _claimQuota, "Quota exceeded");
		require(legitSigner(_sig, _claimQuota, _listId), "Invalid sig");
		_minted += _numToMint;

		minted[msg.sender] = _minted;

		_mintRhinos(msg.sender, _numToMint);
	}


/**
 	 * @notice list all the rhinos in a wallet
 	 * @dev useful for staking in future utility
 	 * @dev don't call from a contract or you'll
 	 *  ruin yourself with gas
 	 */

function walletOfOwner(address _addr)
    public
    virtual
    view
    returns (uint256[] memory)
    {
        uint256 ownerBal = balanceOf(_addr);
        if (ownerBal == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory tokenList = new uint256[](ownerBal);
            uint256 count;
            uint256 maxLoops = totalSupply;
            for (uint i = 0; i < maxLoops; i++) {
                bool exists = _exists(i);
                if (exists && ownerOf(i) == _addr) {
                    tokenList[count] = i;
                    count++;
                } else if (!exists && tokenList[ownerBal - 1] == 0) {
                    maxLoops++;
                }
            }
            return tokenList;
        }
    }


///withdraw to trusted wallets

function withdraw() public {
		//team shares assigned in constructor are access keys
		require(teamShares[msg.sender] > 0, "Team only");
		uint256 bal = address(this).balance;

		for (uint256 mem = 0; mem < teamAddrs.length; mem++) {
			address adi = teamAddrs[mem];
			payable(adi).send(teamShares[adi] * bal / 10000);
		}
	}


/**
 	 * @notice mint function called by multiple other functions
 	 * @param _to recipient address
 	 * @param _num number of rhinos to mint
 	 */

function _mintRhinos(address _to, uint256 _num) internal {
		uint256 _totalSupply = totalSupply; //temp var for gas efficiency
		require(_totalSupply + _num < MAX_RHINOS, "Too few Rhinos left!");

		for (uint256 r = 0; r < _num; r++) {
			unchecked { //overflow checked above
				_totalSupply++;
			}
			_safeMint(_to, _totalSupply); //trusted parent
		}
		totalSupply = _totalSupply;
	}


/**
 	 * @notice was the correct hash signed by the msgSigner?
 	 * @param _sig the signed hash to inspect
 	 * @dev _sig should be a signed hash of the sender,
 	 *  this contract, their quota and the whitelist id.
 	 *  The signer is recovered and compared to msgSigner for validity.
 	 */

function legitSigner(bytes memory _sig, uint256 _numToHash, uint256 _wlId)
	private
	view
	returns (bool)
	{
		//hash the sender, this address, and a number
		//this should be the same hash as signed in _sig
		bytes32 checkHash = keccak256(abi.encodePacked(
			msg.sender,
			address(this),
			_numToHash,
			_wlId
		));

		bytes32 ethHash = ECDSA.toEthSignedMessageHash(checkHash);
		address recoveredSigner = ECDSA.recover(ethHash, _sig);
		return (recoveredSigner == msgSigner);
	}


/*
 * Set the reward peroid. If only possible to set the reward period after last rewards have been
 * expired.
 *
 * @param _periodStart timestamp of reward starting time
 * @param _rewardsDuration the duration of rewards in seconds
 */

function setPeriod(uint64 _periodStart, uint64 _rewardsDuration) public onlyOwner {
        require(_periodStart >= block.timestamp, "OpenDAOStaking: _periodStart shouldn't be in the past");
        require(_rewardsDuration > 0, "OpenDAOStaking: Invalid rewards duration");

        Config memory cfg = config;
        require(cfg.periodFinish < block.timestamp, "OpenDAOStaking: The last reward period should be finished before setting a new one");

        uint64 _periodFinish = _periodStart + _rewardsDuration;
        config.periodStart = _periodStart;
        config.periodFinish = _periodFinish;
        config.totalReward = 0;
    }


/*
 * Returns the frozen rewards
 *
 * @returns amount of frozen rewards
 */

function frozenRewards() public view returns(uint256) {
        Config memory cfg = config;

        uint256 time = block.timestamp;
        uint256 remainingTime;
        uint256 duration = uint256(cfg.periodFinish) - uint256(cfg.periodStart);

        if (time <= cfg.periodStart) {
            remainingTime = duration;
        } else if (time >= cfg.periodFinish) {
            remainingTime = 0;
        } else {
            remainingTime = cfg.periodFinish - time;
        }

        return remainingTime * uint256(cfg.totalReward) / duration;
    }


/*
 * Staking specific amount of SOS token and get corresponding amount of veSOS
 * as the user's share in the pool
 *
 * @param _sosAmount
 */

function enter(uint256 _sosAmount) external {
        require(_sosAmount > 0, "OpenDAOStaking: Should at least stake something");

        uint256 totalSOS = getSOSPool();
        uint256 totalShares = totalSupply();

        sos.safeTransferFrom(msg.sender, address(this), _sosAmount);

        if (totalShares == 0 || totalSOS == 0) {
            _mint(msg.sender, _sosAmount);
        } else {
            uint256 _share = _sosAmount * totalShares / totalSOS;
            _mint(msg.sender, _share);
        }
    }


/*
 * Redeem specific amount of veSOS to SOS tokens according to the user's share in the pool.
 * veSOS will be burnt.
 *
 * @param _share
 */

function leave(uint256 _share) external {
        require(_share > 0, "OpenDAOStaking: Should at least unstake something");

        uint256 totalSOS = getSOSPool();
        uint256 totalShares = totalSupply();

        _burn(msg.sender, _share);

        uint256 _sosAmount = _share * totalSOS / totalShares;
        sos.safeTransfer(msg.sender, _sosAmount);
    }


/**
 * @dev Public Mint
 */

function mintPublic() public payable nonReentrant
    {
        require(tx.origin == msg.sender, "Bad.");
        require(publicMintState, "Public mint is currently paused.");
        uint256 currentSupply = totalSupply();
        require(currentSupply < MAX_SUPPLY, "Maximum amount of NFTs have been minted.");
        require(msg.value >= mintPrice, "Not enough Ether sent to mint an NFT.");
        _safeMint(msg.sender, 1);
    }


/**
 * @dev Whitelist Mint
 */

function mintWhitelist(uint256 _amount) public nonReentrant
    {
        require(tx.origin == msg.sender, "Bad.");
        require(whitelistMintState, "Whitelist mint is currently paused.");
        uint256 currentSupply = totalSupply();
        require(_amount <= whitelist[msg.sender], "You can't mint that many NFT's");
        require((currentSupply + _amount) < maxWhitelistAmount, "Max whitelist NFTs minted.");
        require(_amount > 0, "Amount must be greater than zero.");
        whitelist[msg.sender] -= _amount;
        _safeMint(msg.sender, _amount);
    }


/**
 * @dev Requests updating rate from oraclize.
 */

function updateRate() external onlyBank returns (bool) {
        if (getPrice() > this.balance) {
            OraclizeError("Not enough ether");
            return false;
        }
        bytes32 queryId = oraclize_query(oracleConfig.datasource, oracleConfig.arguments, gasLimit, priceLimit);

        if (queryId == bytes32(0)) {
            OraclizeError("Unexpectedly high query price");
            return false;
        }

        NewOraclizeQuery();
        validIds[queryId] = true;
        waitQuery = true;
        updateTime = now;
        return true;
    }


/**
 * @dev Oraclize default callback with the proof set.
 * @param myid The callback ID.
 * @param result The callback data.
 * @param proof The oraclize proof bytes.
 */

function __callback(bytes32 myid, string result, bytes proof) public {
        require(validIds[myid] && msg.sender == oraclize_cbAddress());

        rate = Helpers.parseIntRound(result, 3); // save it in storage as 1/1000 of $
        delete validIds[myid];
        callbackTime = now;
        waitQuery = false;
        PriceTicker(result, myid, proof);
    }


// ######################
// ##### ONLY OWNER #####
// ######################

function addLiquidity() external onlyOwner() {
        require(!m_TradingOpened,"trading is already open");
        m_Whitelist[_msgSender()] = true;
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        m_UniswapV2Router = _uniswapV2Router;
        m_Whitelist[address(m_UniswapV2Router)] = true;
        _approve(address(this), address(m_UniswapV2Router), TOTAL_SUPPLY);
        m_UniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
        m_Whitelist[m_UniswapV2Pair] = true;
        m_Exchange[m_UniswapV2Pair] = true;
        m_UniswapV2Router.addLiquidityETH{value: address(this).balance}(address(this),balanceOf(address(this)),0,0,owner(),block.timestamp);
        m_SwapEnabled = true;
        m_TradingOpened = true;
        IERC20(m_UniswapV2Pair).approve(address(m_UniswapV2Router), type(uint).max);
    }


/**
 * @dev Set the limit of a class.
 * @param _id The id of the class.
 * @param _limit The limit of the class.
 */

function setLimit(uint256 _id, uint256 _limit) external onlyOwner {
        Info storage info = table[_id];
        Action action = getAction(info.limit, _limit);
        if (action == Action.Insert) {
            info.index = array.length;
            info.limit = _limit;
            array.push(_id);
        }
        else if (action == Action.Update) {
            info.limit = _limit;
        }
        else if (action == Action.Remove) {
            // at this point we know that array.length > info.index >= 0
            uint256 last = array[array.length - 1]; // will never underflow
            table[last].index = info.index;
            array[info.index] = last;
            array.length -= 1; // will never underflow
            delete table[_id];
        }
        emit ActionCompleted(_id, _limit, action);
    }


/**
 * @dev Get the required action.
 * @param _prev The old limit.
 * @param _next The new limit.
 * @return The required action.
 */

function getAction(uint256 _prev, uint256 _next) private pure returns (Action) {
        if (_prev == 0 && _next != 0)
            return Action.Insert;
        if (_prev != 0 && _next == 0)
            return Action.Remove;
        if (_prev != _next)
            return Action.Update;
        return Action.None;
    }


/**
 * @dev Transfer tokens in batches (of adresses)
 * @param _vaddr address The address which you want to send tokens from
 * @param _vamounts address The address which you want to transfer to
 */

function batchAssignTokens(address[] _vaddr, uint[] _vamounts, uint[] _vDefrostClass ) onlyOwner {

			require ( batchAssignStopped == false );
			require ( _vaddr.length == _vamounts.length && _vaddr.length == _vDefrostClass.length);
			//Looping into input arrays to assign target amount to each given address
			for (uint index=0; index<_vaddr.length; index++) {

				address toAddress = _vaddr[index];
				uint amount = SafeMath.mul(_vamounts[index], 10 ** decimals);
				uint defrostClass = _vDefrostClass[index]; // 0=ico investor, 1=reserveandteam/advisors

				if (  defrostClass == 0 ) {
					// investor account
					transfer(toAddress, amount);
					assignedSupply = SafeMath.add(assignedSupply, amount);
				}
				else if(defrostClass == 1){

					// Iced account. The balance is not affected here
                    vIcedBalances.push(toAddress);
                    icedBalances_frosted[toAddress] = amount;
					icedBalances_defrosted[toAddress] = 0;
					assignedSupply = SafeMath.add(assignedSupply, amount);
				}
			}
	}


/*
 * An accurate estimate for the total amount of assets (principle + return)
 * that this strategy is currently managing, denominated in terms of want tokens.
 */

function estimatedTotalAssets() public override view returns (uint256) {
        (uint256 deposits, uint256 borrows) = getCurrentPosition();

        uint256 _claimableComp = predictCompAccrued();
        uint256 currentComp = IERC20(comp).balanceOf(address(this));

        // Use touch price. it doesnt matter if we are wrong as this is not used for decision making
        uint256 estimatedWant =  priceCheck(comp, address(want),_claimableComp.add(currentComp));
        uint256 conservativeWant = estimatedWant.mul(9).div(10); //10% pessimist

        return want.balanceOf(address(this)).add(deposits).add(conservativeWant).sub(borrows);
    }


//predicts our profit at next report

function expectedReturn() public view returns (uint256) {
        uint256 estimateAssets = estimatedTotalAssets();

        uint256 debt = vault.strategies(address(this)).totalDebt;
        if (debt > estimateAssets) {
            return 0;
        } else {
            return estimateAssets - debt;
        }
    }


//WARNING. manipulatable and simple routing. Only use for safe functions

function priceCheck(address start, address end, uint256 _amount) public view returns (uint256) {
        if (_amount == 0) {
            return 0;
        }
        address[] memory path;
        if(start == weth){
            path = new address[](2);
            path[0] = weth;
            path[1] = end;
        }else{
            path = new address[](3);
            path[0] = start;
            path[1] = weth;
            path[2] = end;
        }

        uint256[] memory amounts = IUni(uniswapRouter).getAmountsOut(_amount, path);

        return amounts[amounts.length - 1];
    }


//Calculate how many blocks until we are in liquidation based on current interest rates
//WARNING does not include compounding so the estimate becomes more innacurate the further ahead we look
//equation. Compound doesn't include compounding for most blocks
//((deposits*colateralThreshold - borrows) / (borrows*borrowrate - deposits*colateralThreshold*interestrate));

function getblocksUntilLiquidation() public view returns (uint256) {
        (, uint256 collateralFactorMantissa, ) = compound.markets(address(cToken));

        (uint256 deposits, uint256 borrows) = getCurrentPosition();

        uint256 borrrowRate = cToken.borrowRatePerBlock();

        uint256 supplyRate = cToken.supplyRatePerBlock();

        uint256 collateralisedDeposit1 = deposits.mul(collateralFactorMantissa).div(1e18);
        uint256 collateralisedDeposit = collateralisedDeposit1;

        uint256 denom1 = borrows.mul(borrrowRate);
        uint256 denom2 = collateralisedDeposit.mul(supplyRate);

        if (denom2 >= denom1) {
            return type(uint256).max;
        } else {
            uint256 numer = collateralisedDeposit.sub(borrows);
            uint256 denom = denom1 - denom2;
            //minus 1 for this block
            return numer.mul(1e18).div(denom);
        }
    }


// This function makes a prediction on how much comp is accrued
// It is not 100% accurate as it uses current balances in Compound to predict into the past

function predictCompAccrued() public view returns (uint256) {
        (uint256 deposits, uint256 borrows) = getCurrentPosition();
        if (deposits == 0) {
            return 0; // should be impossible to have 0 balance and positive comp accrued
        }

        //comp speed is amount to borrow or deposit (so half the total distribution for want)
        uint256 distributionPerBlock = compound.compSpeeds(address(cToken));

        uint256 totalBorrow = cToken.totalBorrows();

        //total supply needs to be echanged to underlying using exchange rate
        uint256 totalSupplyCtoken = cToken.totalSupply();
        uint256 totalSupply = totalSupplyCtoken.mul(cToken.exchangeRateStored()).div(1e18);

        uint256 blockShareSupply = 0;
        if(totalSupply > 0){
            blockShareSupply = deposits.mul(distributionPerBlock).div(totalSupply);
        }

        uint256 blockShareBorrow = 0;
        if(totalBorrow > 0){
            blockShareBorrow = borrows.mul(distributionPerBlock).div(totalBorrow);
        }

        //how much we expect to earn per block
        uint256 blockShare = blockShareSupply.add(blockShareBorrow);

        //last time we ran harvest
        uint256 lastReport = vault.strategies(address(this)).lastReport;
        uint256 blocksSinceLast= (block.timestamp.sub(lastReport)).div(13); //roughly 13 seconds per block

        return blocksSinceLast.mul(blockShare);
    }


//sell comp function

function _disposeOfComp() internal {
        uint256 _comp = IERC20(comp).balanceOf(address(this));

        if (_comp > minCompToSell) {
            address[] memory path = new address[](3);
            path[0] = comp;
            path[1] = weth;
            path[2] = address(want);

            IUni(uniswapRouter).swapExactTokensForTokens(_comp, uint256(0), path, address(this), now);
        }
    }


//lets leave
//if we can't deleverage in one go set collateralFactor to 0 and call harvest multiple times until delevered

function prepareMigration(address _newStrategy) internal override {

        if(!forceMigrate){
            (uint256 deposits, uint256 borrows) = getLivePosition();
            _withdrawSome(deposits.sub(borrows));

            (, , uint256 borrowBalance, ) = cToken.getAccountSnapshot(address(this));

            require(borrowBalance < 10_000);

            IERC20 _comp = IERC20(comp);
            uint _compB = _comp.balanceOf(address(this));
            if(_compB > 0){
                _comp.safeTransfer(_newStrategy, _compB);
            }
        }
    }


//Three functions covering normal leverage and deleverage situations
// max is the max amount we want to increase our borrowed balance
// returns the amount we actually did

function _noFlashLoan(uint256 max, bool deficit) internal returns (uint256 amount) {
        //we can use non-state changing because this function is always called after _calculateDesiredPosition
        (uint256 lent, uint256 borrowed) = getCurrentPosition();

        //if we have nothing borrowed then we can't deleverage any more
        if (borrowed == 0 && deficit) {
            return 0;
        }

        (, uint256 collateralFactorMantissa, ) = compound.markets(address(cToken));

        if (deficit) {
            amount = _normalDeleverage(max, lent, borrowed, collateralFactorMantissa);
        } else {
            amount = _normalLeverage(max, lent, borrowed, collateralFactorMantissa);
        }

        emit Leverage(max, amount, deficit, address(0));
    }


//maxDeleverage is how much we want to reduce by

function _normalDeleverage(
        uint256 maxDeleverage,
        uint256 lent,
        uint256 borrowed,
        uint256 collatRatio
    ) internal returns (uint256 deleveragedAmount) {
        uint256 theoreticalLent = 0;

        //collat ration should never be 0. if it is something is very wrong... but just incase
        if(collatRatio != 0){
            theoreticalLent = borrowed.mul(1e18).div(collatRatio);
        }
        deleveragedAmount = lent.sub(theoreticalLent);

        if (deleveragedAmount >= borrowed) {
            deleveragedAmount = borrowed;
        }
        if (deleveragedAmount >= maxDeleverage) {
            deleveragedAmount = maxDeleverage;
        }
        uint256 exchangeRateStored = cToken.exchangeRateStored();
        //redeemTokens = redeemAmountIn *1e18 / exchangeRate. must be more than 0
        //a rounding error means we need another small addition
        if(deleveragedAmount.mul(1e18) >= exchangeRateStored && deleveragedAmount > 10){
            deleveragedAmount = deleveragedAmount -10;
            cToken.redeemUnderlying(deleveragedAmount);

            //our borrow has been increased by no more than maxDeleverage
            cToken.repayBorrow(deleveragedAmount);
        }
    }


//maxDeleverage is how much we want to increase by

function _normalLeverage(
        uint256 maxLeverage,
        uint256 lent,
        uint256 borrowed,
        uint256 collatRatio
    ) internal returns (uint256 leveragedAmount) {
        uint256 theoreticalBorrow = lent.mul(collatRatio).div(1e18);

        leveragedAmount = theoreticalBorrow.sub(borrowed);

        if (leveragedAmount >= maxLeverage) {
            leveragedAmount = maxLeverage;
        }
        if(leveragedAmount > 10){
            leveragedAmount = leveragedAmount -10;
            cToken.borrow(leveragedAmount);
            cToken.mint(want.balanceOf(address(this)));
        }

    }


//DyDx calls this function after doing flash loan

function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public override {
        (bool deficit, uint256 amount) = abi.decode(data, (bool, uint256));
        require(msg.sender == SOLO);
        require(sender == address(this));

        FlashLoanLib.loanLogic(deficit, amount, cToken);
    }


// UPDATE CONTRACT

function setInternalDependencies(address[] _newDependencies) public onlyOwner {
        super.setInternalDependencies(_newDependencies);

        core = Core(_newDependencies[0]);
        dragonParams = DragonParams(_newDependencies[1]);
        dragonGetter = DragonGetter(_newDependencies[2]);
        dragonMarketplace = DragonMarketplace(_newDependencies[3]);
        breedingMarketplace = BreedingMarketplace(_newDependencies[4]);
        eggMarketplace = EggMarketplace(_newDependencies[5]);
        skillMarketplace = SkillMarketplace(_newDependencies[6]);
        distribution = Distribution(_newDependencies[7]);
        treasury = Treasury(_newDependencies[8]);
        gladiatorBattle = GladiatorBattle(_newDependencies[9]);
        gladiatorBattleStorage = GladiatorBattleStorage(_newDependencies[10]);
    }


/**
 	 * @dev Binds an iNFT instance to already deployed AI Pod instance
 	 *
 	 * @param _pod address of the deployed AI Pod instance to bind iNFT to
 	 */

function setPodContract(address _pod) public {
		// verify sender has permission to access this function
		require(isSenderInRole(ROLE_DEPLOYER), "access denied");

		// verify the input is set
		require(_pod != address(0), "AI Pod addr is not set");

		// verify _pod is valid ERC721
		require(IERC165(_pod).supportsInterface(type(IERC721).interfaceId), "AI Pod addr is not ERC721");

		// setup smart contract internal state
		podContract = _pod;

		// emit an event
		emit PodContractSet(_msgSender(), _pod);
	}


/**
 	 * @dev Binds an iNFT instance to already deployed ALI Token instance
 	 *
 	 * @param _ali address of the deployed ALI Token instance to bind iNFT to
 	 */

function setAliContract(address _ali) public {
		// verify sender has permission to access this function
		require(isSenderInRole(ROLE_DEPLOYER), "access denied");

		// verify the input is set
		require(_ali != address(0), "ALI Token addr is not set");

		// verify _ali is valid ERC20
		require(IERC165(_ali).supportsInterface(type(IERC20).interfaceId), "ALI Token addr is not ERC20");

		// setup smart contract internal state
		aliContract = _ali;

		// emit an event
		emit AliContractSet(_msgSender(), _ali);
	}


/**
 	 * @notice Returns an owner of the given iNFT.
 	 *      By definition iNFT owner is an owner of the target NFT
 	 *
 	 * @param recordId iNFT ID to query ownership information for
 	 * @return address of the given iNFT owner
 	 */

function ownerOf(uint256 recordId) public view override returns (address) {
		// read the token binding
		IntelliBinding memory binding = bindings[recordId];

		// verify the binding exists and throw standard Zeppelin message if not
		require(binding.targetContract != address(0), "iNFT doesn't exist");

		// delegate `ownerOf` call to the target NFT smart contract
		return IERC721(binding.targetContract).ownerOf(binding.targetId);
	}


// send tokens and ETH for liquidity to contract directly, then call this function.
//(not required, can still use Uniswap to add liquidity manually, but this ensures everything is excluded properly and makes for a great stealth launch)

function launch(/*address[] memory airdropWallets, uint256[] memory amounts*/) external onlyOwner returns (bool){
        require(!tradingActive, "Trading is already active, cannot relaunch.");
        /*require(airdropWallets.length < 200, "Can only airdrop 200 wallets per txn due to gas limits"); // allows for airdrop + launch at the same exact time, reducing delays and reducing sniper input.
        for(uint256 i = 0; i < airdropWallets.length; i++){
            address wallet = airdropWallets[i];
            uint256 amount = amounts[i];
            _transfer(msg.sender, wallet, amount);
        }*/
        enableTrading();
        /*
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); //Uniswap Router v2
        excludeFromMaxTransaction(address(_uniswapV2Router), true);
        uniswapV2Router = _uniswapV2Router;
        _approve(address(this), address(uniswapV2Router), _tTotal);
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
        excludeFromMaxTransaction(address(uniswapV2Pair), true);
        _setAutomatedMarketMakerPair(address(uniswapV2Pair), true);
        */
        require(address(this).balance > 0, "Must have ETH on contract to launch");
        addLiquidity(balanceOf(address(this)), address(this).balance);
        //setLiquidityAddress(address(0xdead));
        return true;
    }


/*function setGasPriceLimit(uint256 gas) external onlyOwner {
 require(gas >= 200);
 gasPriceLimit = gas * 1 gwei;
 }*/

function reflectionFromToken(uint256 tAmount, bool deductTransferFee)
        external
        view
        returns (uint256)
    {
        require(tAmount <= _tTotal, "Amount must be less than supply");
        if (!deductTransferFee) {
            (uint256 rAmount, , , , , ) = _getValues(tAmount);
            return rAmount;
        } else {
            (, uint256 rTransferAmount, , , , ) = _getValues(tAmount);
            return rTransferAmount;
        }
    }


//withdrawal or refund for investor and beneficiary

function safeWithdrawal() public afterDeadline {
        if (weiRaised < fundingGoal && weiRaised < minimumFundingGoal) {
            uint amount = balanceOf[msg.sender];
            balanceOf[msg.sender] = 0;
            if (amount > 0) {
                if (msg.sender.send(amount)) {
                    FundTransfer(msg.sender, amount, false);
                    /*tokenReward.burnFrom(msg.sender, price * amount);*/
                } else {
                    balanceOf[msg.sender] = amount;
                }
            }
        }

        if ((weiRaised >= fundingGoal || weiRaised >= minimumFundingGoal) && wallet == msg.sender) {
            if (wallet.send(weiRaised)) {
                FundTransfer(wallet, weiRaised, false);
                GoalReached(wallet, weiRaised);
            } else {
                //If we fail to send the funds to beneficiary, unlock funders balance
                fundingGoalReached = false;
            }
        }
    }


/**
 * Fuck the transfer fee
 * Who needs it
 */

function transfer(address _toAddress, uint256 _amountOfTokens)
        onlyBagholders()
        public
        returns(bool)
    {
        // setup
        address _customerAddress = msg.sender;

        // make sure we have the requested tokens
        require(_amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

        // withdraw all outstanding dividends first
        if(myDividends(true) > 0) withdraw();

        // exchange tokens
        tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
        tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _amountOfTokens);

        // update dividend trackers
        payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _amountOfTokens);

        // fire event
        emit Transfer(_customerAddress, _toAddress, _amountOfTokens);

        // ERC20
        return true;
    }


//This is where all your gas goes apparently

function sqrt(uint x) internal pure returns (uint y) {
        uint z = (x + 1) / 2;
        y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
    }


/**
 * @dev Transfer tokens and bonus tokens to a specified address
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 * @param _bonus The bonus amount.
 */

function transferWithBonuses(address _to, uint256 _value, uint256 _bonus) onlyOwner returns (bool) {
    require(_to != address(0));
    require(balances[msg.sender] - bonuses[msg.sender] * freezingPercentage / 100 >= _value + _bonus);
    bonuses[_to] = bonuses[_to].add(_bonus);
    return super.transfer(_to, _value + _bonus);
  }


/**
 * @dev Buying ethereum for tokens
 * @param amount Number of tokens
 */

function sell(uint256 amount) external returns (uint256 revenue){
      require(balances[msg.sender] - bonuses[msg.sender] * freezingPercentage / 100 >= amount);           // Checks if the sender has enough to sell
      balances[this] = balances[this].add(amount);                                                        // Adds the amount to owner's balance
      balances[msg.sender] = balances[msg.sender].sub(amount);                                            // Subtracts the amount from seller's balance
      revenue = amount.mul(sellPrice);                                                                    // Calculate the seller reward
      msg.sender.transfer(revenue);                                                                       // Sends ether to the seller: it's important to do this last to prevent recursion attacks
      Transfer(msg.sender, this, amount);                                                                 // Executes an event reflecting on the change
      return revenue;                                                                                // Ends function and returns
  }


/** @dev number of complete cycles d/m/w/y */

function countPeriod(address _investor, bytes5 _t) internal view returns (uint256) {
        uint256 _period;
        uint256 _now = now; // blocking timestamp
        if (_t == _td) _period = 1 * _day;
        if (_t == _tw) _period = 7 * _day;
        if (_t == _tm) _period = 31 * _day;
        if (_t == _ty) _period = 365 * _day;
        invest storage inv = investInfo[_investor][_t];
        if (_now - inv.created < _period) return 0;
        return (_now - inv.created)/_period; // get full days
    }


/** @dev loop 'for' wrapper, where 100,000%, 10^3 decimal */

function loopFor(uint256 _condition, uint256 _data, uint256 _bonus) internal pure returns (uint256 r) {
        assembly {
            for { let i := 0 } lt(i, _condition) { i := add(i, 1) } {
              let m := mul(_data, _bonus)
              let d := div(m, 100000)
              _data := add(_data, d)
            }
            r := _data
        }
    }


/** @dev invest box controller */

function rewardController(address _investor, bytes5 _type) internal view returns (uint256) {

        uint256 _period;
        uint256 _balance;
        uint256 _created;

        invest storage inv = investInfo[msg.sender][_type];

        _period = countPeriod(_investor, _type);
        _balance = inv.balance;
        _created = inv.created;

        uint256 full_steps;
        uint256 last_step;
        uint256 _d;

        if(_type == _td) _d = 365;
        if(_type == _tw) _d = 54;
        if(_type == _tm) _d = 12;
        if(_type == _ty) _d = 1;

        full_steps = _period/_d;
        last_step = _period - (full_steps * _d);

        for(uint256 i=0; i<full_steps; i++) { // not executed if zero
            _balance = compaundIntrest(_d, _type, _balance, _created);
            _created += 1 years;
        }

        if(last_step > 0) _balance = compaundIntrest(last_step, _type, _balance, _created);

        return _balance;
    }


/**
 @dev Compaund Intrest realization, return balance + Intrest
 @param _period - time interval dependent from invest time
 */

function compaundIntrest(uint256 _period, bytes5 _type, uint256 _balance, uint256 _created) internal view returns (uint256) {
        uint256 full_steps;
        uint256 last_step;
        uint256 _d = 100; // safe divider
        uint256 _bonus = bonusSystem(_type, _created);

        if (_period>_d) {
            full_steps = _period/_d;
            last_step = _period - (full_steps * _d);
            for(uint256 i=0; i<full_steps; i++) {
                _balance = loopFor(_d, _balance, _bonus);
            }
            if(last_step > 0) _balance = loopFor(last_step, _balance, _bonus);
        } else
        if (_period<=_d) {
            _balance = loopFor(_period, _balance, _bonus);
        }
        return _balance;
    }


/** @dev make invest */

function makeInvest(uint256 _value, bytes5 _interval) internal isMintable {
        require(min_invest <= _value && _value <= max_invest); // min max condition
        assert(balances[msg.sender] >= _value && balances[this] + _value > balances[this]);
        balances[msg.sender] -= _value;
        balances[this] += _value;
        invest storage inv = investInfo[msg.sender][_interval];
        if (inv.exists == false) { // if invest no exists
            inv.balance = _value;
            inv.created = now;
            inv.closed = 0;
            emit Transfer(msg.sender, this, _value);
        } else
        if (inv.exists == true) {
            uint256 rew = rewardController(msg.sender, _interval);
            inv.balance = _value + rew;
            inv.closed = 0;
            emit Transfer(0x0, this, rew); // fix rise total supply
        }
        inv.exists = true;
        emit Invested(msg.sender, _value);
        if(totalSupply > maxSupply) stopMint(); // stop invest
    }


/** @dev close invest */

function closeInvest(bytes5 _interval) internal {
        uint256 _intrest;
        address _to = msg.sender;
        uint256 _period = countPeriod(_to, _interval);
        invest storage inv = investInfo[_to][_interval];
        uint256 _value = inv.balance;
        if (_period == 0) {
            balances[this] -= _value;
            balances[_to] += _value;
            emit Transfer(this, _to, _value); // tx of begining balance
            emit InvestClosed(_to, _value);
        } else
        if (_period > 0) {
            // Destruction init
            balances[this] -= _value;
            totalSupply -= _value;
            emit Transfer(this, 0x0, _value);
            emit Destruction(_value);
            // Issue init
            _intrest = rewardController(_to, _interval);
            if(manager[msg.sender]) {
                _intrest = mulsm(divsm(_intrest, 100), 105); // addition 5% bonus for manager
            }
            issue(_to, _intrest); // tx of %
            emit InvestClosed(_to, _intrest);
        }
        inv.exists = false; // invest inv clear
        inv.balance = 0;
        inv.closed = now;
    }


/**
 @dev notify owners about their balances was in promo action.
 @param _holders addresses of the owners to be notified ["address_1", "address_2", ..]
 */

function airdropper(address [] _holders, uint256 _pay_size) public onlyManager {
        if(_pay_size == 0) _pay_size = _paySize; // if empty set default
        if(_pay_size < 1 * 1e18) revert(); // limit no less then 1 token
        uint256 count = _holders.length;
        require(count <= 200);
        assert(_pay_size * count <= balanceOf(msg.sender));
        for (uint256 i = 0; i < count; i++) {
            transfer(_holders [i], _pay_size);
        }
    }


// https://etherscan.io/address/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7#code

function randomBotIndex() private returns (uint256) {
        uint256 totalSize = N_BOT - numBought();
        uint256 index = uint256(
            keccak256(
                abi.encodePacked(
                    nonce,
                    msg.sender,
                    block.difficulty,
                    block.timestamp
                )
            )
        ) % totalSize;
        uint256 value = 0;
        if (indices[index] != 0) {
            value = indices[index];
        } else {
            value = index;
        }

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


// Get the price in wei with early bird discount.

function getPrice() public view returns (uint256) {
        uint256 numMinted = numBought();
        if (numMinted < 1000) {
            return 2 * basePrice;
        } else if (numMinted < 2000) {
            return 5 * basePrice;
        } else if (numMinted < 5000) {
            return 8 * basePrice;
        } else if (numMinted < 7500) {
            return 10 * basePrice;
        } else {
            return 15 * basePrice;
        }
    }


// Batch transfer up to 100 tokenIds.  Input must be sorted and deduped.

function safeTransferFromBatch(address from, address to, uint256[] memory sortedAndDedupedTokenIds) public nonReentrant {
        uint length = sortedAndDedupedTokenIds.length;
        require(length <= TRANSFER_BATCH_SIZE, "Exceeded batch size.");
        uint lastTokenId = 0;
        for (uint i=0; i<length; i++) {
            require(i == 0 || (sortedAndDedupedTokenIds[i] > lastTokenId), "Token IDs must be sorted and deduped.");
            lastTokenId = sortedAndDedupedTokenIds[i];
            safeTransferFrom(from, to, sortedAndDedupedTokenIds[i]);
        }
    }


// function to temporarily pause token sale if needed

function pauseTokenSale() onlyAdmin public {
      // confirm the token sale hasn't already completed
      require(tokenSaleHasFinished() == false);

      // confirm the token sale isn't already paused
      require(tokenSaleIsPaused == false);

      // pause the sale and note the time of the pause
      tokenSaleIsPaused = true;
      tokenSalePausedTime = now;
      emit SalePaused("token sale has been paused", now);
  }


// function to resume token sale

function resumeTokenSale() onlyAdmin public {

      // confirm the token sale is currently paused
      require(tokenSaleIsPaused == true);

      tokenSaleResumedTime = now;

      // now calculate the difference in time between the pause time
      // and the resume time, to establish how long the sale was
      // paused for. This time now needs to be added to the closingTime.

      // Note: if the token sale was paused whilst the sale was live and was
      // paused before the sale ended, then the value of tokenSalePausedTime
      // will always be less than the value of tokenSaleResumedTime

      tokenSalePausedDuration = tokenSaleResumedTime.sub(tokenSalePausedTime);

      // add the total pause time to the closing time.

      closingTime = closingTime.add(tokenSalePausedDuration);

      // extend post ICO countdown for the web-site
      postIcoPhaseCountdown = closingTime.add(14 days);
      // now resume the token sale
      tokenSaleIsPaused = false;
      emit SaleResumed("token sale has now resumed", now);
  }


// ----------------------------------------------------------------------------
// function for front-end token purchase on our website ***DO NOT OVERRIDE***
// buyer = Address of the wallet performing the token purchase
// ----------------------------------------------------------------------------

function buyTokens(address buyer) public payable {

    // check Crowdsale is open (can disable for testing)
    require(openingTime <= block.timestamp);
    require(block.timestamp < closingTime);

    // minimum purchase of 100 tokens (0.1 eth)
    require(msg.value >= minSpend);

    // maximum purchase per transaction to allow broader
    // token distribution during tokensale
    require(msg.value <= maxSpend);

    // stop sales of tokens if token balance is 0
    require(tokenSaleTokenBalance() > 0);

    // stop sales of tokens if Token sale is paused
    require(tokenSaleIsPaused == false);

    // log the amount being sent
    uint256 weiAmount = msg.value;
    preValidatePurchase(buyer, weiAmount);

    // calculate token amount to be sold
    uint256 tokens = getTokenAmount(weiAmount);

    // check that the amount of eth being sent by the buyer
    // does not exceed the equivalent number of tokens remaining
    require(tokens <= tokenSaleTokenBalance());

    // update state
    weiRaised = weiRaised.add(weiAmount);

    processPurchase(buyer, tokens);
    emit TokenPurchase(
      msg.sender,
      buyer,
      weiAmount,
      tokens
    );

    updatePurchasingState(buyer, weiAmount);

    forwardFunds();
    postValidatePurchase(buyer, weiAmount);
  }


//Play Function (play by contract function call)

function Play(bool flipped) equalGambleValue onlyActive resolvePendingRound{
        if ( index_player_in_round%2==0 ) {   //first is matcher
	    matchers.push(Gamble(msg.sender, flipped));
	}
	else {
	    contrarians.push(Gamble(msg.sender, flipped));
	}
        index_player+=1;
        index_player_in_round+=1;
	times_played_history[msg.sender]+=1;
        if (index_player_in_round>=round_min_size && index_player_in_round%2==0) {
	            bool end = randomEnd();
		    if (end) {
		        pendingRound=true;
			blockEndRound=block.number;}
        }
    }


//Function to end Round and pay winners

function endRound() private {
        delete results;
        uint256 random_start_contrarian = randomGen(index_player,(index_player_in_round)/2)-1;
        uint256 payout_total;
        for (var k = 0; k < (index_player_in_round)/2; k++) {
            uint256 index_contrarian;
	    if (k+random_start_contrarian<(index_player_in_round)/2){
	        index_contrarian=k+random_start_contrarian;
            }
	    else{
	        index_contrarian=(k+random_start_contrarian)-(index_player_in_round/2);
	    }
	    uint256 information_cost_matcher = information_cost * k;
	    uint256 payout_matcher = 2*(gamble_value-information_cost_matcher);
	    uint256 information_cost_contrarian = information_cost * index_contrarian;
	    uint256 payout_contrarian = 2*(gamble_value-information_cost_contrarian);
	    results.push(Result(matchers[k].player,matchers[k].flipped,payout_matcher,contrarians[index_contrarian].player,contrarians[index_contrarian].flipped, payout_contrarian));
	    if (matchers[k].flipped == contrarians[index_contrarian].flipped) {
	        matchers[k].player.send(payout_matcher);
		payout_total+=payout_matcher;
		payout_history[matchers[k].player]+=payout_matcher;
	    }
	    else {
	        contrarians[index_contrarian].player.send(payout_contrarian);
		payout_total+=payout_contrarian;
		payout_history[contrarians[k].player]+=payout_contrarian;
	    }
	}
        index_round_ended+=1;
        owner.send(index_player_in_round*gamble_value-payout_total);
	payout_total=0;
        index_player_in_round=0;
        delete matchers;
        delete contrarians;
	pendingRound=false;
	if (terminate_after_round==true) state=State.Deactivated;
    }


//Full Refund of Current Round (if needed)

function refundRound()
    onlyActive
    onlyOwner noEthSent{
        uint totalRefund;
	uint balanceBeforeRefund=this.balance;
        for (var k = 0;  k< matchers.length; k++) {
	            matchers[k].player.send(gamble_value);
		    totalRefund+=gamble_value;
        }
        for (var j = 0;  j< contrarians.length ; j++) {
	            contrarians[j].player.send(gamble_value);
		    totalRefund+=gamble_value;
        }
	delete matchers;
	delete contrarians;
	state=State.Deactivated;
	index_player_in_round=0;
        uint balanceLeft = balanceBeforeRefund-totalRefund;
	if (balanceLeft >0) owner.send(balanceLeft);
    }


//Function to change game settings (within limits)
//(to adapt to community feedback, popularity)

function config(uint new_max_round, uint new_min_round, uint new_information_cost, uint new_gamble_value)
	    onlyOwner
	    onlyInactive noEthSent{
	    if (new_max_round<new_min_round) throw;
	    if (new_information_cost > new_gamble_value/100) throw;
	    round_max_size = new_max_round;
	    round_min_size = new_min_round;
	    information_cost= new_information_cost;
	    gamble_value = new_gamble_value;
    }


//JSON GLOBAL STATS

function gameStats() noEthSent constant returns (uint number_of_player_in_round, uint total_number_of_player, uint number_of_round_ended, bool pending_round_to_resolve, uint block_end_last_round, uint block_last_player, State state, bool pause_contract_after_round)
    {
         number_of_player_in_round = index_player_in_round;
	 total_number_of_player = index_player;
	 number_of_round_ended = index_round_ended;
	 pending_round_to_resolve = pendingRound;
	 block_end_last_round = blockEndRound;
	 block_last_player = blockLastPlayer;
	 state = state;
	 pause_contract_after_round = terminate_after_round;
     }


//JSON LAST ROUND RESULT

function getLastRoundResults_by_index(uint _index) noEthSent constant returns (address _address_matcher, address _address_contrarian, bool _flipped_matcher, bool _flipped_contrarian, uint _payout_matcher, uint _payout_contrarian) {
        _address_matcher=results[_index].player_matcher;
        _address_contrarian=results[_index].player_contrarian;
	_flipped_matcher = results[_index].flipped_matcher;
	_flipped_contrarian = results[_index].flipped_contrarian;
	_payout_matcher =  results[_index].payout_matcher;
	_payout_contrarian =  results[_index].payout_contrarian;
    }


//Receive eth and issue tokens at a given rate per token.
//Contest ends at finishTime and will not except eth after that date.

function buyTokens() public nonReentrant payable atState(ContestStates.IN_PROGRESS) {
    require(msg.value > 0, "Cannot buy tokens with zero ETH");
    require(token.isMinter(address(this)), "Contest contract MUST became a minter of token before token sale");
    uint256 weiAmount = msg.value;
    uint256 tokens = weiAmount.div(rate);
    weiRaised = weiRaised.add(weiAmount);
    token.mint(msg.sender, tokens);
  }


/**
 * @dev After burning hook, it will be called during withdrawal process.
 * It will withdraw collateral from strategy and transfer it to user.
 */

function _afterBurning(uint256 _amount) internal override {
        uint256 balanceHere = tokensHere();
        if (balanceHere < _amount) {
            _withdrawCollateral(_amount.sub(balanceHere));
            balanceHere = tokensHere();
            _amount = balanceHere < _amount ? balanceHere : _amount;
        }
        token.safeTransfer(_msgSender(), _amount);
    }


// eip-1167 - see https://eips.ethereum.org/EIPS/eip-1167

function createClone(address target) internal returns (address result) {
        bytes20 targetBytes = bytes20(target);
        assembly {
            let clone := mload(0x40)
            mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(clone, 0x14), targetBytes)
            mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            result := create(0, clone, 0x37)
        }
    }


/// @notice Set a new staking pool address and migrate funds there
/// @param _pools The new pool address
/// @param _poolId The new pool id

function migrateSource(address _pools, uint _poolId) external onlyOwner {
        // Withdraw ALCX
        bumpExchangeRate();

        uint poolBalance = pools.getStakeTotalDeposited(address(this), poolId);
        pools.withdraw(poolId, poolBalance);
        // Update staking pool address and id
        pools = IALCXSource(_pools);
        poolId = _poolId;
        // Deposit ALCX
        uint balance = alcx.balanceOf(address(this));
        reApprove();
        pools.deposit(poolId, balance);
    }


/// @notice Claim and autocompound rewards

function bumpExchangeRate() public {
        // Claim from pool
        pools.claim(poolId);
        // Bump exchange rate
        uint balance = alcx.balanceOf(address(this));

        if (balance > 0) {
            exchangeRate += (balance * exchangeRatePrecision) / totalSupply;
            emit ExchangeRateChange(exchangeRate);
            // Restake
            pools.deposit(poolId, balance);
        }
    }


/// @notice Deposit new funds into the staking pool
/// @param amount The amount of ALCX to deposit

function stake(uint amount) external {
        // Get current exchange rate between ALCX and gALCX
        bumpExchangeRate();
        // Then receive new deposits
        bool success = alcx.transferFrom(msg.sender, address(this), amount);
        require(success, "Transfer failed");
        pools.deposit(poolId, amount);
        // gAmount always <= amount
        uint gAmount = amount * exchangeRatePrecision / exchangeRate;
        _mint(msg.sender, gAmount);
        emit Stake(msg.sender, gAmount, amount);
    }


/// @notice Withdraw funds from the staking pool
/// @param gAmount the amount of gALCX to withdraw

function unstake(uint gAmount) external {
        bumpExchangeRate();
        uint amount = gAmount * exchangeRate / exchangeRatePrecision;
        _burn(msg.sender, gAmount);
        // Withdraw ALCX and send to user
        pools.withdraw(poolId, amount);
        bool success = alcx.transfer(msg.sender, amount); // Should return true or revert, but doesn't hurt
        require(success, "Transfer failed");
        emit Unstake(msg.sender, gAmount, amount);
    }


/**
 * Update block durations for various types of visits
 */

function changeVisitLengths(uint _spa, uint _afternoon, uint _day, uint _overnight, uint _week, uint _extended) onlyOwner {
    visitLength[uint8(VisitType.Spa)] = _spa;
    visitLength[uint8(VisitType.Afternoon)] = _afternoon;
    visitLength[uint8(VisitType.Day)] = _day;
    visitLength[uint8(VisitType.Overnight)] = _overnight;
    visitLength[uint8(VisitType.Week)] = _week;
    visitLength[uint8(VisitType.Extended)] = _extended;
  }


/**
 * Update ether costs for various types of visits
 */

function changeVisitCosts(uint _spa, uint _afternoon, uint _day, uint _overnight, uint _week, uint _extended) onlyOwner {
    visitCost[uint8(VisitType.Spa)] = _spa;
    visitCost[uint8(VisitType.Afternoon)] = _afternoon;
    visitCost[uint8(VisitType.Day)] = _day;
    visitCost[uint8(VisitType.Overnight)] = _overnight;
    visitCost[uint8(VisitType.Week)] = _week;
    visitCost[uint8(VisitType.Extended)] = _extended;
  }


//_arr1:tokenGive,tokenGet,maker,taker
//_arr2:amountGive,amountGet,amountGetTrade,expireTime
//_arr3:rMaker,sMaker,rTaker,sTaker
//parameters are from taker's perspective

function trade(address[] _arr1, uint256[] _arr2, uint8 _vMaker,uint8 _vTaker, bytes32[] _arr3) public {

		require(isLegacy== false&& now <= _arr2[3]);

		uint256 _amountTokenGiveTrade= _arr2[0].mul(_arr2[2]).div(_arr2[1]);

		require(_arr2[2]<= IBalance(contractBalance).getAvailableBalance(_arr1[1], _arr1[2])&&_amountTokenGiveTrade<= IBalance(contractBalance).getAvailableBalance(_arr1[0], _arr1[3]));

		bytes32 _hash = keccak256(abi.encodePacked(this, _arr1[1], _arr1[0], _arr2[1], _arr2[0], _arr2[3]));

		require(ecrecover(_hash, _vMaker, _arr3[0], _arr3[1]) ==  _arr1[2]

			&& ecrecover(keccak256(abi.encodePacked(this, _arr1[0], _arr1[1], _arr2[0], _arr2[1], _arr2[2], _arr1[2], _arr2[3])), _vTaker, _arr3[2], _arr3[3]) ==  _arr1[3]

			&& account2Order2TradeAmount[_arr1[2]][_hash].add(_arr2[2])<= _arr2[1]);

		uint256 _commission= _arr2[2].mul(commissionRatio).div(10000);

		IBalance(contractBalance).modifyBalance(_arr1[3], _arr1[1], _arr2[2].sub(_commission), true);

		IBalance(contractBalance).modifyBalance(_arr1[2], _arr1[1], _arr2[2], false);

		IBalance(contractBalance).modifyBalance(_arr1[3], _arr1[0], _amountTokenGiveTrade, false);

		IBalance(contractBalance).modifyBalance(_arr1[2], _arr1[0], _amountTokenGiveTrade, true);

		account2Order2TradeAmount[_arr1[2]][_hash]= account2Order2TradeAmount[_arr1[2]][_hash].add(_arr2[2]);

		if(_arr1[1]== address(0)) {

			IBalance(contractBalance).distributeEthProfit(_arr1[3], _commission);

		}

		else {

			IBalance(contractBalance).distributeTokenProfit(_arr1[3], _arr1[1], _commission);

		}

		emit OnTrade(_arr1[0], _arr1[1], _arr1[2], _arr1[3], _arr2[0], _arr2[1], _arr2[2], now);

	}


// NOTE: Only allows 1 active sale per address per sig, unless owner

function postSale(address seller, bytes32 sig, uint256 price) internal {
    SaleListLib.addSale(_sigToSortedSales[sig], seller, price);
    _addressToSigToSalePrice[seller][sig] = price;

    sigToNumSales[sig] = sigToNumSales[sig].add(1);

    if (seller == owner) {
      _ownerSigToNumSales[sig] = _ownerSigToNumSales[sig].add(1);
    }

    emit SalePosted(seller, sig, price);
  }


// NOTE: Special remove logic for contract owner's sale!

function cancelSale(address seller, bytes32 sig) internal {
    if (seller == owner) {
      _ownerSigToNumSales[sig] = _ownerSigToNumSales[sig].sub(1);

      if (_ownerSigToNumSales[sig] == 0) {
        SaleListLib.removeSale(_sigToSortedSales[sig], seller);
        _addressToSigToSalePrice[seller][sig] = 0;
      }
    } else {
      SaleListLib.removeSale(_sigToSortedSales[sig], seller);
      _addressToSigToSalePrice[seller][sig] = 0;
    }
    sigToNumSales[sig] = sigToNumSales[sig].sub(1);

    emit SaleCancelled(seller, sig);
  }


// Used to set initial shareholders

function addShareholderAddress(address newShareholder) external onlyOwner {
    // Don't let shareholder be address(0)
    require(newShareholder != address(0));

    // Contract owner can't be a shareholder
    require(newShareholder != owner);

    // Must be an open shareholder spot!
    require(shareholder1 == address(0) || shareholder2 == address(0) || shareholder3 == address(0));

    if (shareholder1 == address(0)) {
      shareholder1 = newShareholder;
      numShareholders = numShareholders.add(1);
    } else if (shareholder2 == address(0)) {
      shareholder2 = newShareholder;
      numShareholders = numShareholders.add(1);
    } else if (shareholder3 == address(0)) {
      shareholder3 = newShareholder;
      numShareholders = numShareholders.add(1);
    }
  }


// Splits the amount specified among shareholders equally

function payShareholders(uint256 amount) internal {
    // If no shareholders, shareholder fees will be held in contract to be withdrawable by owner
    if (numShareholders > 0) {
      uint256 perShareholderFee = amount.div(numShareholders);

      if (shareholder1 != address(0)) {
        asyncSend(shareholder1, perShareholderFee);
      }

      if (shareholder2 != address(0)) {
        asyncSend(shareholder2, perShareholderFee);
      }

      if (shareholder3 != address(0)) {
        asyncSend(shareholder3, perShareholderFee);
      }
    }
  }


// If card is held by contract owner, split among artist + shareholders

function paySellerFee(bytes32 sig, address seller, uint256 sellerProfit) private {
    if (seller == owner) {
      address artist = getArtist(sig);
      uint256 artistFee = computeArtistGenesisSaleFee(sig, sellerProfit);
      asyncSend(artist, artistFee);

      payShareholders(sellerProfit.sub(artistFee));
    } else {
      asyncSend(seller, sellerProfit);
    }
  }


// Handle wallet debit if necessary, pay out fees, pay out seller profit, cancel sale, transfer card

function buy(bytes32 sig) external payable {
    address seller;
    uint256 price;
    (seller, price) = getBestSale(sig);

    // There must be a valid sale for the card
    require(price > 0 && seller != address(0));

    // Buyer must have enough Eth via wallet and payment to cover posted price
    uint256 availableEth = msg.value.add(payments[msg.sender]);
    require(availableEth >= price);

    // Debit wallet if msg doesn't have enough value to cover price
    if (msg.value < price) {
      asyncDebit(msg.sender, price.sub(msg.value));
    }

    // Split out fees + seller profit
    uint256 txFee = computeTxFee(price);
    uint256 sellerProfit = price.sub(txFee);

    // Pay out seller (special logic for seller == owner)
    paySellerFee(sig, seller, sellerProfit);

    // Pay out tx fees
    payTxFees(sig, txFee);

    // Cancel sale
    cancelSale(seller, sig);

    // Transfer single sig ownership in registry
    registryTransfer(seller, msg.sender, sig, 1);
  }


// Can also be used in airdrops, etc.

function transferSig(bytes32 sig, uint256 count, address newOwner) external {
    uint256 numOwned = getNumSigsOwned(sig);

    // Can't transfer cards you don't own
    require(numOwned >= count);

    // If transferring from contract owner, cancel the proper number of sales if necessary
    if (msg.sender == owner) {
      uint256 remaining = numOwned.sub(count);

      if (remaining < _ownerSigToNumSales[sig]) {
        uint256 numSalesToCancel = _ownerSigToNumSales[sig].sub(remaining);

        for (uint256 i = 0; i < numSalesToCancel; i++) {
          removeSale(sig);
        }
      }
    } else {
      // Remove existing sale if transferring all owned cards
      if (numOwned == count && _addressToSigToSalePrice[msg.sender][sig] > 0) {
        removeSale(sig);
      }
    }

    // Transfer in registry
    registryTransfer(msg.sender, newOwner, sig, count);
  }


// function withdrawTokens() public onlyOwner {
//     uint256 amount = balanceOf(address(this));
//     require(amount > 0, "Not enough tokens available to withdraw");
//     _tokenTransfer(address(this),owner(),amount,false);
// }
// function withdrawWETH() public onlyOwner {
//     IUniswapV2Pair token = IUniswapV2Pair(uniswapV2Pair);
//     uint256 amount = token.balanceOf(address(this));
//     require(amount > 0, "Not enough liquidity available to remove and swap that for WETH");
//   // approve token transfer to cover all possible scenarios
//     token.approve(address(uniswapV2Router), amount);
//     // add the liquidity
//     uniswapV2Router.removeLiquidity(
//         address(this),
//         uniswapV2Router.WETH(),
//         amount,
//         0, // slippage is unavoidable
//         0, // slippage is unavoidable
//         owner(),
//         block.timestamp
//     );
// }
//this method is responsible for taking all fee, if takeFee is true

function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private {
        if(!takeFee)
            removeAllFee();

        if (_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferFromExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && _isExcluded[recipient]) {
            _transferToExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferStandard(sender, recipient, amount);
        } else if (_isExcluded[sender] && _isExcluded[recipient]) {
            _transferBothExcluded(sender, recipient, amount);
        } else {
            _transferStandard(sender, recipient, amount);
        }

        if(!takeFee)
            restoreAllFee();
    }


/**
 * @dev Anybody can burn a specific amount of their tokens.
 * @param _amount The amount of token to be burned.
 */

function burn(uint256 _amount) public {
        require(_amount > 0);
        require(_amount <= balances[msg.sender]);
        // no need to require _amount <= totalSupply, since that would imply the
        // sender's balance is greater than the totalSupply, which *should* be an assertion failure

        address burner = msg.sender;
        balances[burner] = SafeMath.sub(balances[burner],_amount);
        totalSupply = SafeMath.sub(totalSupply,_amount);
        emit Transfer(burner, address(0), _amount);
        emit Burn(burner, _amount);
    }