/**
 	 * @notice Performs the burning of gToken shares upon the withdrawal of
 	 *         the reserve token. The actual amount of the reserve token to
 	 *         be received can be calculated using the
 	 *         calcWithdrawalCostFromShares function. In every withdrawal,
 	 *         1% of the shares is retained in terms of withdrawal fee.
 	 *         Half of it is immediately burned and the other half is
 	 *         provided to the locked liquidity pool.
 	 * @param _grossShares The gross amount of this gToken shares being
 	 *                     redeemed in the operation.
 	 */

function withdraw(uint256 _grossShares) public override nonReentrant
	{
		address _from = msg.sender;
		require(_grossShares > 0, "shares must be greater than 0");
		(uint256 _cost, uint256 _feeShares) = GFormulae._calcWithdrawalCostFromShares(_grossShares, totalReserve(), totalSupply(), withdrawalFee());
		require(_cost > 0, "cost must be greater than 0");
		require(_prepareWithdrawal(_cost), "not available at the moment");
		_cost = G.min(_cost, G.getBalance(reserveToken));
		G.pushFunds(reserveToken, _from, _cost);
		_burn(_from, _grossShares);
		_mint(address(this), _feeShares.div(2));
	}


/**
 * @dev gives square root of given x.
 */

function sqrt(uint256 x)
    internal
    pure
    returns (uint256 y)
    {
        uint256 z = ((add(x,1)) / 2);
        y = x;
        while (z < y)
        {
            y = z;
            z = ((add((x / z),z)) / 2);
        }
    }


/**
 * @dev x to the power of y
 */

function pwr(uint256 x, uint256 y)
    internal
    pure
    returns (uint256)
    {
        if (x==0)
            return (0);
        else if (y==0)
            return (1);
        else
        {
            uint256 z = x;
            for (uint256 i=1; i < y; i++)
                z = mul(z,x);
            return (z);
        }
    }


/**
 * @dev emergency buy uses last stored affiliate ID and team snek
 */

function()
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // buy core
        buyCore(_pID, plyr_[_pID].laff, 2, _eventData_);
    }


/**
 * @dev converts all incoming ethereum to keys.
 * -functionhash- 0x8f38f309 (using ID for affiliate)
 * -functionhash- 0x98a0871d (using address for affiliate)
 * -functionhash- 0xa65b37a1 (using name for affiliate)
 * @param _affCode the ID/address/name of the player who gets the affiliate fee
 * @param _team what team is the player playing for?
 */

function buyXid(uint256 _affCode, uint256 _team)
    isActivated()
    isHuman()
    isWithinLimits(msg.value)
    public
    payable
    {
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

            // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // verify a valid team was selected
        _team = verifyTeam(_team);

        // buy core
        buyCore(_pID, _affCode, _team, _eventData_);
    }


/**
 * @dev essentially the same as buy, but instead of you sending ether
 * from your wallet, it uses your unwithdrawn earnings.
 * -functionhash- 0x349cdcac (using ID for affiliate)
 * -functionhash- 0x82bfc739 (using address for affiliate)
 * -functionhash- 0x079ce327 (using name for affiliate)
 * @param _affCode the ID/address/name of the player who gets the affiliate fee
 * @param _team what team is the player playing for?
 * @param _eth amount of earnings to use (remainder returned to gen vault)
 */

function reLoadXid(uint256 _affCode, uint256 _team, uint256 _eth)
    isActivated()
    isHuman()
    isWithinLimits(_eth)
    public
    {
        // set up our tx event data
        F3Ddatasets.EventReturns memory _eventData_;

        // fetch player ID
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

            // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // verify a valid team was selected
        _team = verifyTeam(_team);

        // reload core
        reLoadCore(_pID, _affCode, _team, _eth, _eventData_);
    }


/**
 * @dev use these to register names.  they are just wrappers that will send the
 * registration requests to the PlayerBook contract.  So registering here is the
 * same as registering there.  UI will always display the last name you registered.
 * but you will still own all previously registered names to use as affiliate
 * links.
 * - must pay a registration fee.
 * - name must be unique
 * - names will be converted to lowercase
 * - name cannot start or end with a space
 * - cannot have more than 1 space in a row
 * - cannot be only numbers
 * - cannot start with 0x
 * - name must be at least 1 char
 * - max length of 32 characters long
 * - allowed characters: a-z, 0-9, and space
 * -functionhash- 0x921dec21 (using ID for affiliate)
 * -functionhash- 0x3ddd4698 (using address for affiliate)
 * -functionhash- 0x685ffd83 (using name for affiliate)
 * @param _nameString players desired name
 * @param _affCode affiliate ID, address, or name of who referred you
 * @param _all set to true if you want this to push your info to all games
 * (this might cost a lot of gas)
 */

function registerNameXID(string _nameString, uint256 _affCode, bool _all)
    isHuman()
    public
    payable
    {
        bytes32 _name = _nameString.nameFilter();
        address _addr = msg.sender;
        uint256 _paid = msg.value;
        (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXIDFromDapp.value(_paid)(_addr, _name, _affCode, _all);

        uint256 _pID = pIDxAddr_[_addr];

        // fire event
        emit F3Devents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
    }


/**
 * @dev return the price buyer will pay for next 1 individual key.
 * -functionhash- 0x018a25e8
 * @return price for next key bought (in wei format)
 */

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

        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return ( (round_[_rID].keys.add(1000000000000000000)).ethRec(1000000000000000000) );
        else // rounds over.  need price for new round
            return ( 75000000000000 ); // init
    }


/**
 * @dev returns time left.  dont spam this, you'll ddos yourself from your node
 * provider
 * -functionhash- 0xc7e284b8
 * @return time left in seconds
 */

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

        // grab time
        uint256 _now = now;

        if (_now < round_[_rID].end)
            if (_now > round_[_rID].strt + rndGap_)
                return( (round_[_rID].end).sub(_now) );
            else
                return( (round_[_rID].strt + rndGap_).sub(_now) );
        else
            return(0);
    }


/**
 * @dev returns player earnings per vaults
 * -functionhash- 0x63066434
 * @return winnings vault
 * @return general vault
 * @return affiliate vault
 */

function getPlayerVaults(uint256 _pID)
    public
    view
    returns(uint256 ,uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        // if round has ended.  but round end has not been run (so contract has not distributed winnings)
        if (now > round_[_rID].end && round_[_rID].ended == false && round_[_rID].plyr != 0)
        {
            // if player is winner
            if (round_[_rID].plyr == _pID)
            {
                return
                (
                (plyr_[_pID].win).add( ((round_[_rID].pot).mul(48)) / 100 ),
                (plyr_[_pID].gen).add(  getPlayerVaultsHelper(_pID, _rID).sub(plyrRnds_[_pID][_rID].mask)   ),
                plyr_[_pID].aff
                );
                // if player is not the winner
            } else {
                return
                (
                plyr_[_pID].win,
                (plyr_[_pID].gen).add(  getPlayerVaultsHelper(_pID, _rID).sub(plyrRnds_[_pID][_rID].mask)  ),
                plyr_[_pID].aff
                );
            }

            // if round is still going on, or round has ended and round end has been ran
        } else {
            return
            (
            plyr_[_pID].win,
            (plyr_[_pID].gen).add(calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd)),
            plyr_[_pID].aff
            );
        }
    }


/**
 * @dev returns all current round info needed for front end
 * -functionhash- 0x747dff42
 * @return eth invested during ICO phase
 * @return round id
 * @return total keys for round
 * @return time round ends
 * @return time round started
 * @return current pot
 * @return current team ID & player ID in lead
 * @return current player in leads address
 * @return current player in leads name
 * @return whales eth in for round
 * @return bears eth in for round
 * @return sneks eth in for round
 * @return bulls eth in for round
 * @return airdrop tracker # & airdrop pot
 */

function getCurrentRoundInfo()
    public
    view
    returns(uint256, uint256, uint256, uint256, uint256, uint256, uint256, address, bytes32, uint256, uint256, uint256, uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        return
        (
        round_[_rID].ico,               //0
        _rID,                           //1
        round_[_rID].keys,              //2
        round_[_rID].end,               //3
        round_[_rID].strt,              //4
        round_[_rID].pot,               //5
        (round_[_rID].team + (round_[_rID].plyr * 10)),     //6
        plyr_[round_[_rID].plyr].addr,  //7
        plyr_[round_[_rID].plyr].name,  //8
        rndTmEth_[_rID][0],             //9
        rndTmEth_[_rID][1],             //10
        rndTmEth_[_rID][2],             //11
        rndTmEth_[_rID][3],             //12
        airDropTracker_ + (airDropPot_ * 1000)              //13
        );
    }


/**
 * @dev returns player info based on address.  if no address is given, it will
 * use msg.sender
 * -functionhash- 0xee0b5d8b
 * @param _addr address of the player you want to lookup
 * @return player ID
 * @return player name
 * @return keys owned (current round)
 * @return winnings vault
 * @return general vault
 * @return affiliate vault
 	 * @return player round eth
 */

function getPlayerInfoByAddress(address _addr)
    public
    view
    returns(uint256, bytes32, uint256, uint256, uint256, uint256, uint256)
    {
        // setup local rID
        uint256 _rID = rID_;

        if (_addr == address(0))
        {
            _addr == msg.sender;
        }
        uint256 _pID = pIDxAddr_[_addr];

        return
        (
        _pID,                               //0
        plyr_[_pID].name,                   //1
        plyrRnds_[_pID][_rID].keys,         //2
        plyr_[_pID].win,                    //3
        (plyr_[_pID].gen).add(calcUnMaskedEarnings(_pID, plyr_[_pID].lrnd)),       //4
        plyr_[_pID].aff,                    //5
        plyrRnds_[_pID][_rID].eth           //6
        );
    }


/**
 * @dev returns the amount of keys you would get given an amount of eth.
 * -functionhash- 0xce89c80c
 * @param _rID round ID you want price for
 * @param _eth amount of eth sent in
 * @return keys received
 */

function calcKeysReceived(uint256 _rID, uint256 _eth)
    public
    view
    returns(uint256)
    {
        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return ( (round_[_rID].eth).keysRec(_eth) );
        else // rounds over.  need keys for new round
            return ( (_eth).keys() );
    }


/**
 * @dev returns current eth price for X keys.
 * -functionhash- 0xcf808000
 * @param _keys number of keys desired (in 18 decimal format)
 * @return amount of eth needed to send
 */

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

        // grab time
        uint256 _now = now;

        // are we in a round?
        if (_now > round_[_rID].strt + rndGap_ && (_now <= round_[_rID].end || (_now > round_[_rID].end && round_[_rID].plyr == 0)))
            return ( (round_[_rID].keys.add(_keys)).ethRec(_keys) );
        else // rounds over.  need price for new round
            return ( (_keys).eth() );
    }


/**
 	 * @dev receives name/player info from names contract
 */

function receivePlayerInfo(uint256 _pID, address _addr, bytes32 _name, uint256 _laff)
    external
    {
        require (msg.sender == address(PlayerBook), "your not playerNames contract... hmmm..");
        if (pIDxAddr_[_addr] != _pID)
            pIDxAddr_[_addr] = _pID;
        if (pIDxName_[_name] != _pID)
            pIDxName_[_name] = _pID;
        if (plyr_[_pID].addr != _addr)
            plyr_[_pID].addr = _addr;
        if (plyr_[_pID].name != _name)
            plyr_[_pID].name = _name;
        if (plyr_[_pID].laff != _laff)
            plyr_[_pID].laff = _laff;
        if (plyrNames_[_pID][_name] == false)
            plyrNames_[_pID][_name] = true;
    }


/**
 * @dev gets existing or registers new pID.  use this when a player may be new
 * @return pID
 */

function determinePID(F3Ddatasets.EventReturns memory _eventData_)
    private
    returns (F3Ddatasets.EventReturns)
    {
        uint256 _pID = pIDxAddr_[msg.sender];
        // if player is new to this version of fomo3d
        if (_pID == 0)
        {
            // grab their player ID, name and last aff ID, from player names contract
            _pID = PlayerBook.getPlayerID(msg.sender);
            bytes32 _name = PlayerBook.getPlayerName(_pID);
            uint256 _laff = PlayerBook.getPlayerLAff(_pID);

            // set up player account
            pIDxAddr_[msg.sender] = _pID;
            plyr_[_pID].addr = msg.sender;

            if (_name != "")
            {
                pIDxName_[_name] = _pID;
                plyr_[_pID].name = _name;
                plyrNames_[_pID][_name] = true;
            }

            if (_laff != 0 && _laff != _pID)
                plyr_[_pID].laff = _laff;

            // set the new player bool to true
            _eventData_.compressedData = _eventData_.compressedData + 1;
        }
        return (_eventData_);
    }


/**
 * @dev decides if round end needs to be run & new round started.  and if
 * player unmasked earnings from previously played rounds need to be moved.
 */

function managePlayer(uint256 _pID, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns (F3Ddatasets.EventReturns)
    {
        // if player has played a previous round, move their unmasked earnings
        // from that round to gen vault.
        if (plyr_[_pID].lrnd != 0)
            updateGenVault(_pID, plyr_[_pID].lrnd);

        // update player's last round played
        plyr_[_pID].lrnd = rID_;

        // set the joined round bool to true
        _eventData_.compressedData = _eventData_.compressedData + 10;

        return(_eventData_);
    }


/**
 * @dev moves any unmasked earnings to gen vault.  updates earnings mask
 */

function updateGenVault(uint256 _pID, uint256 _rIDlast)
    private
    {
        uint256 _earnings = calcUnMaskedEarnings(_pID, _rIDlast);
        if (_earnings > 0)
        {
            // put in gen vault
            plyr_[_pID].gen = _earnings.add(plyr_[_pID].gen);
            // zero out their earnings by updating mask
            plyrRnds_[_pID][_rIDlast].mask = _earnings.add(plyrRnds_[_pID][_rIDlast].mask);
        }
    }


/**
 * @dev updates round timer based on number of whole keys bought.
 */

function updateTimer(uint256 _keys, uint256 _rID)
    private
    {
        // grab time
        uint256 _now = now;

        // calculate time based on number of keys bought
        uint256 _newTime;
        if (_now > round_[_rID].end && round_[_rID].plyr == 0)
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)).add(_now);
        else
            _newTime = (((_keys) / (1000000000000000000)).mul(rndInc_)).add(round_[_rID].end);

        // compare to max and set new end time
        if (_newTime < (rndMax_).add(_now))
            round_[_rID].end = _newTime;
        else
            round_[_rID].end = rndMax_.add(_now);
    }


/**
 * @dev distributes eth based on fees to com, aff, and p3d
 */

function distributeExternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _affID, uint256 _team, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns(F3Ddatasets.EventReturns)
    {
        // pay 2% out to community rewards
        uint256 _com = _eth / 50;

        // distribute share to affiliate
        uint256 _aff = _eth / 10;

        // decide what to do with affiliate share of fees
        // affiliate must not be self, and must have a name registered
        if (_affID != _pID && plyr_[_affID].name != '') {
            plyr_[_affID].aff = _aff.add(plyr_[_affID].aff);
            emit F3Devents.onAffiliatePayout(_affID, plyr_[_affID].addr, plyr_[_affID].name, _rID, _pID, _aff, now);
        } else {
            _com = _com.add(_aff);
        }

        uint256 _mkt = _eth.mul(fees_[_team].marketing) / 100;
        _com = _com.add(_mkt);
        owner.transfer(_com);

        _eventData_.mktAmount = _mkt;
//        _eventData_.comAmount = _com;
        return(_eventData_);
    }


/**
 * @dev distributes eth based on fees to gen and pot
 */

function distributeInternal(uint256 _rID, uint256 _pID, uint256 _eth, uint256 _team, uint256 _keys, F3Ddatasets.EventReturns memory _eventData_)
    private
    returns(F3Ddatasets.EventReturns)
    {
        // calculate gen share
        uint256 _gen = (_eth.mul(fees_[_team].gen)) / 100;

        // toss 1% into airdrop pot
        uint256 _air = (_eth / 100);
        airDropPot_ = airDropPot_.add(_air);

        uint256 cut = (fees_[_team].marketing).add(13);
        _eth = _eth.sub(_eth.mul(cut) / 100);

        // calculate pot
        uint256 _pot = _eth.sub(_gen);

        // distribute gen share (thats what updateMasks() does) and adjust
        // balances for dust.
        uint256 _dust = updateMasks(_rID, _pID, _gen, _keys);
        if (_dust > 0) {
            _gen = _gen.sub(_dust);
        }

        // add eth to pot
        round_[_rID].pot = _pot.add(_dust).add(round_[_rID].pot);

        // set up event data
        _eventData_.genAmount = _gen.add(_eventData_.genAmount);
        _eventData_.potAmount = _pot;

        return(_eventData_);
    }


/**
 * @dev adds up unmasked earnings, & vault earnings, sets them all to 0
 * @return earnings in wei format
 */

function withdrawEarnings(uint256 _pID)
    private
    returns(uint256)
    {
        // update gen vault
        updateGenVault(_pID, plyr_[_pID].lrnd);

        // from vaults
        uint256 _earnings = (plyr_[_pID].win).add(plyr_[_pID].gen).add(plyr_[_pID].aff);
        if (_earnings > 0)
        {
            plyr_[_pID].win = 0;
            plyr_[_pID].gen = 0;
            plyr_[_pID].aff = 0;
        }

        return(_earnings);
    }


/**
 * @dev prepares compression data and fires event for buy or reload tx's
 */

function endTx(uint256 _pID, uint256 _team, uint256 _eth, uint256 _keys, F3Ddatasets.EventReturns memory _eventData_)
    private
    {
        _eventData_.compressedData = _eventData_.compressedData + (now * 1000000000000000000) + (_team * 100000000000000000000000000000);
        _eventData_.compressedIDs = _eventData_.compressedIDs + _pID + (rID_ * 10000000000000000000000000000000000000000000000000000);

        emit F3Devents.onEndTx
        (
            _eventData_.compressedData,
            _eventData_.compressedIDs,
            plyr_[_pID].name,
            msg.sender,
            _eth,
            _keys,
            _eventData_.winnerAddr,
            _eventData_.winnerName,
            _eventData_.amountWon,
            _eventData_.newPot,
            _eventData_.mktAmount,
//            _eventData_.comAmount,
            _eventData_.genAmount,
            _eventData_.potAmount,
            airDropPot_
        );
    }


/**
 * @dev Escrow finalization task, called when finalize() is called.
 */

function _finalization() internal {
        if (goalReached()) {
            //escrow used to get transferred here, but that allows people to withdraw tokens before LP is created
        } else {
            _escrow.enableRefunds();
        }

        super._finalization();
    }


/**
 * @dev Transfer token for a specified address
 * @param _token erc20 The address of the ERC20 contract
 * @param _to address The address which you want to transfer to
 * @param _value uint256 the _value of tokens to be transferred
 */

function safeTransfer(IERC20 _token, address _to, uint256 _value) internal returns (bool) {
        uint256 prevBalance = _token.balanceOf(address(this));

        require(prevBalance >= _value, "Insufficient funds");

        _token.transfer(_to, _value);

        require(prevBalance - _value == _token.balanceOf(address(this)), "Transfer failed");

        return true;
    }


/**
 * @dev Allow contract owner to withdraw ERC-20 balance from contract
 * while still splitting royalty payments to all other team members.
 * in the event ERC-20 tokens are paid to the contract.
 * @param _tokenContract contract of ERC-20 token to withdraw
 * @param _amount balance to withdraw according to balanceOf of ERC-20 token
 */

function withdrawAllERC20(address _tokenContract, uint256 _amount) public onlyOwner {
    require(_amount > 0);
    IERC20 tokenContract = IERC20(_tokenContract);
    require(tokenContract.balanceOf(address(this)) >= _amount, 'Contract does not own enough tokens');

    for(uint i=0; i < payableAddressCount; i++ ) {
        tokenContract.transfer(payableAddresses[i], (_amount * payableFees[i]) / 100);
    }
 }


/**
 * @dev Withdraw tokens only after crowdsale ends.
 * @param beneficiary Whose tokens will be withdrawn.
 */

function withdrawTokens(address beneficiary) public {
       require(goalReached(), "RefundableCrowdsale: goal not reached");
       //require(hasClosed(), "PostDeliveryCrowdsale: not closed");
       require(finalized(), "Withdraw Tokens: crowdsale not finalized");
        uint256 amount = _balances[beneficiary];
        require(amount > 0, "PostDeliveryCrowdsale: beneficiary is not due any tokens");

        _balances[beneficiary] = 0;
        //_vault.transfer(token(), beneficiary, amount); //so taxes are avoided
        _deliverTokens(address(beneficiary), amount);
    }


/**
 * @dev Extend crowdsale.
 * @param newClosingTime Crowdsale closing time
 */

function _extendTime(uint256 newClosingTime) internal {
        require(!hasClosed(), "TimedCrowdsale: already closed");
        // solhint-disable-next-line max-line-length
        require(newClosingTime > _closingTime, "TimedCrowdsale: new closing time is before current closing time");

        emit TimedCrowdsaleExtended(_closingTime, newClosingTime);
        _closingTime = newClosingTime;
    }


/**
 	 * @notice Performs the minting of gcToken shares upon the deposit of the
 	 *         cToken underlying asset. The funds will be pulled in by this
 	 *         contract, therefore they must be previously approved. This
 	 *         function builds upon the GTokenBase deposit function. See
 	 *         GTokenBase.sol for further documentation.
 	 * @param _underlyingCost The amount of the underlying asset being
 	 *                        deposited in the operation.
 	 */

function depositUnderlying(uint256 _underlyingCost) public override nonReentrant
	{
		address _from = msg.sender;
		require(_underlyingCost > 0, "underlying cost must be greater than 0");
		uint256 _cost = GCFormulae._calcCostFromUnderlyingCost(_underlyingCost, exchangeRate());
		(uint256 _netShares, uint256 _feeShares) = GFormulae._calcDepositSharesFromCost(_cost, totalReserve(), totalSupply(), depositFee());
		require(_netShares > 0, "shares must be greater than 0");
		G.pullFunds(underlyingToken, _from, _underlyingCost);
		GC.safeLend(reserveToken, _underlyingCost);
		require(_prepareDeposit(_cost), "not available at the moment");
		_mint(_from, _netShares);
		_mint(address(this), _feeShares.div(2));
	}


/**
 	 * @notice Performs the burning of gcToken shares upon the withdrawal of
 	 *         the underlying asset. This function builds upon the
 	 *         GTokenBase withdrawal function. See GTokenBase.sol for
 	 *         further documentation.
 	 * @param _grossShares The gross amount of this gcToken shares being
 	 *                     redeemed in the operation.
 	 */

function withdrawUnderlying(uint256 _grossShares) public override nonReentrant
	{
		address _from = msg.sender;
		require(_grossShares > 0, "shares must be greater than 0");
		(uint256 _cost, uint256 _feeShares) = GFormulae._calcWithdrawalCostFromShares(_grossShares, totalReserve(), totalSupply(), withdrawalFee());
		uint256 _underlyingCost = GCFormulae._calcUnderlyingCostFromCost(_cost, exchangeRate());
		require(_underlyingCost > 0, "underlying cost must be greater than 0");
		require(_prepareWithdrawal(_cost), "not available at the moment");
		_underlyingCost = G.min(_underlyingCost, GC.getLendAmount(reserveToken));
		GC.safeRedeem(reserveToken, _underlyingCost);
		G.pushFunds(underlyingToken, _from, _underlyingCost);
		_burn(_from, _grossShares);
		_mint(address(this), _feeShares.div(2));
	}


// @notice Multiplies two numbers, throws on overflow.

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }


// @notice Integer division of two numbers, truncating the quotient.

function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    // uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return a / b;
  }


/**
 * @notice Deposits funds in the current mint batch
 * @param _amount Amount of 3cr3CRV to use for minting
 * @param _depositFor User that gets the shares attributed to (for use in zapper contract)
 */

function depositForMint(uint256 _amount, address _depositFor)
    external
    nonReentrant
    whenNotPaused
    onlyApprovedContractOrEOA
  {
    require(
      _hasRole(keccak256("ButterZapper"), msg.sender) || msg.sender == _depositFor,
      "you cant transfer other funds"
    );
    require(threeCrv.balanceOf(msg.sender) >= _amount, "insufficent balance");
    threeCrv.transferFrom(msg.sender, address(this), _amount);
    _deposit(_amount, currentMintBatchId, _depositFor);
  }


/**
 * @notice This function allows a user to withdraw their funds from a batch before that batch has been processed
 * @param _batchId From which batch should funds be withdrawn from
 * @param _amountToWithdraw Amount of Butter or 3CRV to be withdrawn from the queue (depending on mintBatch / redeemBatch)
 * @param _withdrawFor User that gets the shares attributed to (for use in zapper contract)
 */

function withdrawFromBatch(
    bytes32 _batchId,
    uint256 _amountToWithdraw,
    address _withdrawFor
  ) external {
    address recipient = _getRecipient(_withdrawFor);

    Batch storage batch = batches[_batchId];
    uint256 accountBalance = accountBalances[_batchId][_withdrawFor];
    require(batch.claimable == false, "already processed");
    require(accountBalance >= _amountToWithdraw, "account has insufficient funds");

    //At this point the account balance is equal to the supplied token and can be used interchangeably
    accountBalances[_batchId][_withdrawFor] = accountBalance - _amountToWithdraw;
    batch.suppliedTokenBalance = batch.suppliedTokenBalance - _amountToWithdraw;
    batch.unclaimedShares = batch.unclaimedShares - _amountToWithdraw;

    if (batch.batchType == BatchType.Mint) {
      threeCrv.safeTransfer(recipient, _amountToWithdraw);
    } else {
      setToken.safeTransfer(recipient, _amountToWithdraw);
    }
    emit WithdrawnFromBatch(_batchId, _amountToWithdraw, _withdrawFor);
  }


/**
 * @notice Claims funds after the batch has been processed (get Butter from a mint batch and 3CRV from a redeem batch)
 * @param _batchId Id of batch to claim from
 * @param _claimFor User that gets the shares attributed to (for use in zapper contract)
 */

function claim(bytes32 _batchId, address _claimFor) external returns (uint256) {
    (address recipient, BatchType batchType, uint256 accountBalance, uint256 tokenAmountToClaim) = _prepareClaim(
      _batchId,
      _claimFor
    );
    //Transfer token
    if (batchType == BatchType.Mint) {
      setToken.safeTransfer(recipient, tokenAmountToClaim);
    } else {
      //We only want to apply a fee on redemption of Butter
      //Sweethearts are partner addresses that we want to exclude from this fee
      if (!sweethearts[_claimFor]) {
        //Fee is deducted from threeCrv -- This allows it to work with the Zapper
        //Fes are denominated in BasisPoints
        uint256 fee = (tokenAmountToClaim * redemptionFee.rate) / 10_000;
        redemptionFee.accumulated += fee;
        tokenAmountToClaim = tokenAmountToClaim - fee;
      }
      threeCrv.safeTransfer(recipient, tokenAmountToClaim);
    }
    emit Claimed(recipient, batchType, accountBalance, tokenAmountToClaim);

    return tokenAmountToClaim;
  }


/**
 * @notice Claims BTR after batch has been processed and stakes it in Staking.sol
 * @param _batchId Id of batch to claim from
 * @param _claimFor User that gets the shares attributed to (for use in zapper contract)
 */

function claimAndStake(bytes32 _batchId, address _claimFor) external {
    (address recipient, BatchType batchType, uint256 accountBalance, uint256 tokenAmountToClaim) = _prepareClaim(
      _batchId,
      _claimFor
    );
    emit Claimed(recipient, batchType, accountBalance, tokenAmountToClaim);

    //Transfer token
    require(batchType == BatchType.Mint, "Can only stake BTR");
    staking.stakeFor(tokenAmountToClaim, recipient);
  }


/**
 * @notice sets approval for contracts that require access to assets held by this contract
 */

function setApprovals() external {
    (address[] memory tokenAddresses, ) = setBasicIssuanceModule.getRequiredComponentUnitsForIssue(setToken, 1e18);

    for (uint256 i; i < tokenAddresses.length; i++) {
      IERC20 curveLpToken = curvePoolTokenPairs[tokenAddresses[i]].crvLPToken;
      CurveMetapool curveMetapool = curvePoolTokenPairs[tokenAddresses[i]].curveMetaPool;
      YearnVault yearnVault = YearnVault(tokenAddresses[i]);

      _maxApprove(curveLpToken, address(curveMetapool));
      _maxApprove(curveLpToken, address(yearnVault));
      _maxApprove(threeCrv, address(curveMetapool));
    }
    _maxApprove(IERC20(address(setToken)), address(staking));
  }


/**
 * @notice returns the min amount of butter that should be minted given an amount of 3crv
 * @dev this controls slippage in the minting process
 */

function getMinAmountToMint(
    uint256 _valueOfBatch,
    uint256 _valueOfComponentsPerUnit,
    uint256 _slippage
  ) public pure returns (uint256) {
    uint256 _mintAmount = (_valueOfBatch * 1e18) / _valueOfComponentsPerUnit;
    uint256 _delta = (_mintAmount * _slippage) / 10_000;
    return _mintAmount - _delta;
  }


// ACTIONS WITH OWN TOKEN

function sendToNest(
        address _sender,
        uint256 _eggId
    ) external onlyController returns (bool, uint256, uint256, address) {
        require(!getter.isEggOnSale(_eggId), "egg is on sale");
        require(core.isEggOwner(_sender, _eggId), "not an egg owner");

        uint256 _hatchingPrice = treasury.hatchingPrice();
        treasury.takeGold(_hatchingPrice);
        if (getter.getDragonsAmount() > 2997) { // 2997 + 2 (in the nest) + 1 (just sent) = 3000 dragons without gold burning
            treasury.burnGold(_hatchingPrice.div(2));
        }

        return core.sendToNest(_eggId);
    }


/**
 * @notice returns the value of butter in virtualPrice
 */

function valueOfComponents(address[] memory _tokenAddresses, uint256[] memory _quantities)
    public
    view
    returns (uint256)
  {
    uint256 value;
    for (uint256 i = 0; i < _tokenAddresses.length; i++) {
      value +=
        (((YearnVault(_tokenAddresses[i]).pricePerShare() *
          curvePoolTokenPairs[_tokenAddresses[i]].curveMetaPool.get_virtual_price()) / 1e18) * _quantities[i]) /
        1e18;
    }
    return value;
  }


/**
 * @notice makes sure only zapper or user can withdraw from accout_ and returns the recipient of the withdrawn token
 * @param _account is the address which gets withdrawn from
 * @dev returns recipient of the withdrawn funds
 * @dev By default a user should set _account to their address
 * @dev If zapper is used to withdraw and swap for a user the msg.sender will be zapper and _account is the user which we withdraw from. The zapper than sends the swapped funds afterwards to the user
 */

function _getRecipient(address _account) internal view returns (address) {
    //Make sure that only zapper can withdraw from someone else
    require(_hasRole(keccak256("ButterZapper"), msg.sender) || msg.sender == _account, "you cant transfer other funds");

    //Set recipient per default to _account
    address recipient = _account;

    //set the recipient to zapper if its called by the zapper
    if (_hasRole(keccak256("ButterZapper"), msg.sender)) {
      recipient = msg.sender;
    }
    return recipient;
  }


/**
 * @notice Generates the next batch id for new deposits
 * @param _currentBatchId takes the current mint or redeem batch id
 * @param _batchType BatchType of the newly created id
 */

function _generateNextBatch(bytes32 _currentBatchId, BatchType _batchType) internal returns (bytes32) {
    bytes32 id = _generateNextBatchId(_currentBatchId);
    batchIds.push(id);
    Batch storage batch = batches[id];
    batch.batchType = _batchType;
    batch.batchId = id;

    if (BatchType.Mint == _batchType) {
      currentMintBatchId = id;
      batch.suppliedTokenAddress = address(threeCrv);
      batch.claimableTokenAddress = address(setToken);
    }
    if (BatchType.Redeem == _batchType) {
      currentRedeemBatchId = id;
      batch.suppliedTokenAddress = address(setToken);
      batch.claimableTokenAddress = address(threeCrv);
    }
    return id;
  }


/**
 * @notice Deposit either Butter or 3CRV in their respective batches
 * @param _amount The amount of 3CRV or Butter a user is depositing
 * @param _currentBatchId The current reedem or mint batch id to place the funds in the next batch to be processed
 * @param _depositFor User that gets the shares attributed to (for use in zapper contract)
 * @dev This function will be called by depositForMint or depositForRedeem and simply reduces code duplication
 */

function _deposit(
    uint256 _amount,
    bytes32 _currentBatchId,
    address _depositFor
  ) internal {
    Batch storage batch = batches[_currentBatchId];

    //Add the new funds to the batch
    batch.suppliedTokenBalance = batch.suppliedTokenBalance + _amount;
    batch.unclaimedShares = batch.unclaimedShares + _amount;
    accountBalances[_currentBatchId][_depositFor] = accountBalances[_currentBatchId][_depositFor] + _amount;

    //Save the batchId for the user so they can be retrieved to claim the batch
    if (
      accountBatches[_depositFor].length == 0 ||
      accountBatches[_depositFor][accountBatches[_depositFor].length - 1] != _currentBatchId
    ) {
      accountBatches[_depositFor].push(_currentBatchId);
    }

    emit Deposit(_depositFor, _amount);
  }


/**
 * @notice This function checks all requirements for claiming, updates batches and balances and returns the values needed for the final transfer of tokens
 * @param _batchId Id of batch to claim from
 * @param _claimFor User that gets the shares attributed to (for use in zapper contract)
 */

function _prepareClaim(bytes32 _batchId, address _claimFor)
    internal
    returns (
      address,
      BatchType,
      uint256,
      uint256
    )
  {
    Batch storage batch = batches[_batchId];
    require(batch.claimable, "not yet claimable");

    address recipient = _getRecipient(_claimFor);
    uint256 accountBalance = accountBalances[_batchId][_claimFor];
    require(accountBalance <= batch.unclaimedShares, "claiming too many shares");

    //Calculate how many token will be claimed
    uint256 tokenAmountToClaim = (batch.claimableTokenBalance * accountBalance) / batch.unclaimedShares;

    //Subtract the claimed token from the batch
    batch.claimableTokenBalance = batch.claimableTokenBalance - tokenAmountToClaim;
    batch.unclaimedShares = batch.unclaimedShares - accountBalance;
    accountBalances[_batchId][_claimFor] = 0;

    return (recipient, batch.batchType, accountBalance, tokenAmountToClaim);
  }


/**
 * @notice Deposit 3CRV in a curve metapool for its LP-Token
 * @param _amount The amount of 3CRV that gets deposited
 * @param _curveMetapool The metapool where we want to provide liquidity
 */

function _sendToCurve(uint256 _amount, CurveMetapool _curveMetapool) internal {
    //Takes 3CRV and sends lpToken to this contract
    //Metapools take an array of amounts with the exoctic stablecoin at the first spot and 3CRV at the second.
    //The second variable determines the min amount of LP-Token we want to receive (slippage control)
    _curveMetapool.add_liquidity([0, _amount], 0);
  }


/**
 * @notice Withdraws 3CRV for deposited crvLPToken
 * @param _amount The amount of crvLPToken that get deposited
 * @param _curveMetapool The metapool where we want to provide liquidity
 */

function _withdrawFromCurve(uint256 _amount, CurveMetapool _curveMetapool) internal {
    //Takes lp Token and sends 3CRV to this contract
    //The second variable is the index for the token we want to receive (0 = exotic stablecoin, 1 = 3CRV)
    //The third variable determines min amount of token we want to receive (slippage control)
    _curveMetapool.remove_liquidity_one_coin(_amount, 1, 0);
  }


/**
 * @notice Changes the the ProcessingThreshold
 * @param _cooldown Cooldown in seconds
 * @param _mintThreshold Amount of MIM necessary to mint immediately
 * @param _redeemThreshold Amount of Butter necessary to mint immediately
 * @dev The cooldown is the same for redeem and mint batches
 */

function setProcessingThreshold(
    uint256 _cooldown,
    uint256 _mintThreshold,
    uint256 _redeemThreshold
  ) public onlyRole(DAO_ROLE) {
    ProcessingThreshold memory newProcessingThreshold = ProcessingThreshold({
      batchCooldown: _cooldown,
      mintThreshold: _mintThreshold,
      redeemThreshold: _redeemThreshold
    });
    emit ProcessingThresholdUpdated(processingThreshold, newProcessingThreshold);
    processingThreshold = newProcessingThreshold;
  }


/**
 * @notice sets slippage for mint and redeem
 * @param _mintSlippage amount in bps (e.g. 50 = 0.5%)
 * @param _redeemSlippage amount in bps (e.g. 50 = 0.5%)
 */

function setSlippage(uint256 _mintSlippage, uint256 _redeemSlippage) external onlyRole(DAO_ROLE) {
    require(_mintSlippage <= 200 && _redeemSlippage <= 200, "slippage too high");
    Slippage memory newSlippage = Slippage({ mintBps: _mintSlippage, redeemBps: _redeemSlippage });
    emit SlippageUpdated(slippage, newSlippage);
    slippage = newSlippage;
  }


// Assign tokens to investor with locking period

function assignToken(address _investor,uint256 _tokens) external {
        // Tokens assigned by only Angel Sales,PE Sales,Founding Investor and Initiator Team wallets
        require(msg.sender == walletAddresses[0] || msg.sender == walletAddresses[1] || msg.sender == walletAddresses[2] || msg.sender == walletAddresses[3]);

        // Check investor address and tokens.Not allow 0 value
        require(_investor != address(0) && _tokens > 0);
        // Check wallet have enough token balance to assign
        require(_tokens <= balances[msg.sender]);

        // Debit the tokens from the wallet
        balances[msg.sender] = safeSub(balances[msg.sender],_tokens);

        // Assign tokens to the investor
        if(msg.sender == walletAddresses[0] || msg.sender == walletAddresses[1]){
            walletAngelPESales[_investor] = safeAdd(walletAngelPESales[_investor],_tokens);
        }
        else if(msg.sender == walletAddresses[2] || msg.sender == walletAddresses[3]){
            walletFoundingInitiatorSales[_investor] = safeAdd(walletFoundingInitiatorSales[_investor],_tokens);
        }
        else{
            revert();
        }
    }


// For wallet Angel Sales and PE Sales

function getWithdrawableAmountANPES(address _investor) public view returns(uint256) {
        require(startWithdraw != 0);
        // interval in months
        uint interval = safeDiv(safeSub(now,startWithdraw),30 days);
        // total allocatedTokens
        uint _allocatedTokens = safeAdd(walletAngelPESales[_investor],releasedAngelPESales[_investor]);
        // Atleast 6 months
        if (interval < 6) {
            return (0);
        } else if (interval >= 6 && interval < 12) {
            return safeSub(getPercentageAmount(25,_allocatedTokens), releasedAngelPESales[_investor]);
        } else if (interval >= 12 && interval < 18) {
            return safeSub(getPercentageAmount(50,_allocatedTokens), releasedAngelPESales[_investor]);
        } else if (interval >= 18 && interval < 24) {
            return safeSub(getPercentageAmount(75,_allocatedTokens), releasedAngelPESales[_investor]);
        } else if (interval >= 24) {
            return safeSub(_allocatedTokens, releasedAngelPESales[_investor]);
        }
    }


// For wallet Founding Investor and Initiator Team

function getWithdrawableAmountFIIT(address _investor) public view returns(uint256) {
        require(startWithdraw != 0);
        // interval in months
        uint interval = safeDiv(safeSub(now,startWithdraw),30 days);
        // total allocatedTokens
        uint _allocatedTokens = safeAdd(walletFoundingInitiatorSales[_investor],releasedFoundingInitiatorSales[_investor]);
        // Atleast 24 months
        if (interval < 24) {
            return (0);
        } else if (interval >= 24) {
            return safeSub(_allocatedTokens, releasedFoundingInitiatorSales[_investor]);
        }
    }


// Sale of the tokens. Investors can call this method to invest into ITT Tokens

function() payable external {
        // Allow only to invest in ICO stage
        require(startStop);

        //Sorry !! We only allow to invest with minimum 0.5 Ether as value
        require(msg.value >= (0.5 ether));

        // multiply by exchange rate to get token amount
        uint256 calculatedTokens = safeMul(msg.value, tokensPerEther);

        // Wait we check tokens available for assign
        require(balances[ethExchangeWallet] >= calculatedTokens);

        // Call to Internal function to assign tokens
        assignTokens(msg.sender, calculatedTokens);
    }


// Function will transfer the tokens to investor's address
// Common function code for assigning tokens

function assignTokens(address investor, uint256 tokens) internal {
        // Debit tokens from ether exchange wallet
        balances[ethExchangeWallet] = safeSub(balances[ethExchangeWallet], tokens);

        // Assign tokens to the sender
        balances[investor] = safeAdd(balances[investor], tokens);

        // Finally token assigned to sender, log the creation event
        Transfer(ethExchangeWallet, investor, tokens);
    }


//  Transfer `value` ITTokens from sender's account
// `msg.sender` to provided account address `to`.
// @param _to The address of the recipient
// @param _value The number of ITTokens to transfer
// @return Whether the transfer was successful or not

function transfer(address _to, uint _value) public returns (bool ok) {
        //validate receiver address and value.Not allow 0 value
        require(_to != 0 && _value > 0);
        uint256 senderBalance = balances[msg.sender];
        //Check sender have enough balance
        require(senderBalance >= _value);
        senderBalance = safeSub(senderBalance, _value);
        balances[msg.sender] = senderBalance;
        balances[_to] = safeAdd(balances[_to], _value);
        Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * @dev Creates a new token type and assigns _initialSupply to an address
 * NOTE: remove onlyOwner if you want third parties to create new tokens on your contract (which may change your IDs)
 * @param _initialOwner address of the first owner of the token
 * @param _initialSupply amount to supply the first owner
 * @param _uri Optional URI for this token type
 * @param _data Data to pass if receiver is contract
 * @return The newly created token ID
 */

function create(
    address _initialOwner,
    uint256 _initialSupply,
    string calldata _uri,
    bytes calldata _data
  ) external onlyOwner returns (uint256) {

    uint256 _id = _getNextTokenID();
    _incrementTokenTypeId();
    creators[_id] = msg.sender;

    if (bytes(_uri).length > 0) {
      emit URI(_uri, _id);
    }

    _mint(_initialOwner, _id, _initialSupply, _data);
    tokenSupply[_id] = _initialSupply;
    return _id;
  }


/**
 * @dev Mint tokens for each id in _ids
 * @param _to          The address to mint tokens to
 * @param _ids         Array of ids to mint
 * @param _quantities  Array of amounts of tokens to mint per id
 * @param _data        Data to pass if receiver is contract
 */

function batchMint(
    address _to,
    uint256[] memory _ids,
    uint256[] memory _quantities,
    bytes memory _data
  ) public {
    for (uint256 i = 0; i < _ids.length; i++) {
      uint256 _id = _ids[i];
      require(creators[_id] == msg.sender, "ERC1155Tradable#batchMint: ONLY_CREATOR_ALLOWED");
      uint256 quantity = _quantities[i];
      tokenSupply[_id] += quantity;
    }
    _batchMint(_to, _ids, _quantities, _data);
  }


/**
 * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-free listings.
 */

function isApprovedForAll(
    address _owner,
    address _operator
  ) public view returns (bool isOperator) {
    // Whitelist OpenSea proxy contract for easy trading.
    ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
    if (address(proxyRegistry.proxies(_owner)) == _operator) {
      return true;
    }

    return ERC1155.isApprovedForAll(_owner, _operator);
  }


// if accidentally other token was donated to Project Dev

function sendTokenAw(address StandardTokenAddress, address receiver, uint amount){
		if (msg.sender != honestisFort) {
		throw;
		}
		sendTokenAway t = transfers[numTransfers];
		t.coinContract = StandardToken(StandardTokenAddress);
		t.amount = amount;
		t.recipient = receiver;
		t.coinContract.transfer(receiver, amount);
		numTransfers++;
	}


// removed isValidData(_data) modifier since this function is also called from SentEnoughEther modifier
// _data is checked in parent functions!  not checked here!

function getPrice(
    MintData memory _data
  )
    public
    view
    returns (uint256)
  {
    uint256 price = 0;
    uint8 _tokenStyle = _data.tokenStyle;

    // there is a discount to the first DISCOUNT_MINT_MAX STYLE_JAWS mints during WhitelistSale
    //   this check intentionally allows a single overbuy at the end of discounted sale. Without this,
    //   we would require a permint mint quantity to proceed with undiscounted minted. This could
    //   be solved with additional complexity, but a couple extra discounted tokens to enable
    //   a smooth minting experience is a worthwhile tradeoff
    if(
        ( saleState == State.WhitelistSale ) &&
        ( _tokenStyle == STYLE_JAWS ) &&
        ( mintStyle[_tokenStyle].sold < DISCOUNT_MINT_MAX ) // allow a single overbuy at end of discounted sale
      )
    {
      price = mintStyle[_tokenStyle].price - DISCOUNT;
    }
    else
    {
      price = mintStyle[_tokenStyle].price;
    }
    return price;
  }


//
// Does state updates at mint time and on every transfer
//

function updateVoyagerState(address to, uint256 cvMintIndex) private {
        uint256 n_tokens = 0;
        if (!_test_chain_mode) {
            // Query any n's we hold
            n_tokens = n.balanceOf(to);
        }
        uint256 new_voyage_count = voyage_count[cvMintIndex] + 1;

        string memory trunc_addr = addrToString(to, 3);

        // Track in the circular ring buffer
        if (new_voyage_count <= MAX_ADDRESS_HISTORY) {
            address_history[cvMintIndex].push(trunc_addr);
        } else {
            uint256 write_idx = new_voyage_count % MAX_ADDRESS_HISTORY;
            address_history[cvMintIndex][write_idx] = trunc_addr;
        }

        // Accumulate totals of the N's taking the firs token we find
        if (n_tokens > 0) {
            uint256 n_id = (n.tokenOfOwnerByIndex(to, 0));
            uint256 n_first = n.getFirst(n_id);
            n_sum_first[cvMintIndex] += n_first;
        }

        voyage_count[cvMintIndex] = new_voyage_count;
    }


/**
 * @notice Approve `spender` to transfer up to `amount` from `src`
 * @dev This will overwrite the approval amount for `spender`
 *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
 * @param spender The address of the account which may transfer tokens
 * @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
 * @return Whether or not the approval succeeded
 */

function approve(address spender, uint rawAmount) external returns (bool) {
        uint96 amount;
        if (rawAmount == uint(-1)) {
            amount = uint96(-1);
        } else {
            amount = safe96(rawAmount, "Comfi::approve: amount exceeds 96 bits");
        }

        allowances[msg.sender][spender] = amount;

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


/**
 * @notice Triggers an approval from owner to spends
 * @param owner The address to approve from
 * @param spender The address to be approved
 * @param rawAmount The number of tokens that are approved (2^256-1 means infinite)
 * @param deadline The time at which to expire the signature
 * @param v The recovery byte of the signature
 * @param r Half of the ECDSA signature pair
 * @param s Half of the ECDSA signature pair
 */

function permit(address owner, address spender, uint rawAmount, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        uint96 amount;
        if (rawAmount == uint(-1)) {
            amount = uint96(-1);
        } else {
            amount = safe96(rawAmount, "Comfi::permit: amount exceeds 96 bits");
        }

        bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainId(), address(this)));
        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, rawAmount, nonces[owner]++, deadline));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
        address signatory = ecrecover(digest, v, r, s);
        require(signatory != address(0), "Comfi::permit: invalid signature");
        require(signatory == owner, "Comfi::permit: unauthorized");
        require(now <= deadline, "Comfi::permit: signature expired");

        allowances[owner][spender] = amount;

        emit Approval(owner, spender, amount);
    }


/**
 * @notice Transfer `amount` tokens from `src` to `dst`
 * @param src The address of the source account
 * @param dst The address of the destination account
 * @param rawAmount The number of tokens to transfer
 * @return Whether or not the transfer succeeded
 */

function transferFrom(address src, address dst, uint rawAmount) external returns (bool) {
        address spender = msg.sender;
        uint96 spenderAllowance = allowances[src][spender];
        uint96 amount = safe96(rawAmount, "Comfi::approve: amount exceeds 96 bits");

        if (spender != src && spenderAllowance != uint96(-1)) {
            uint96 newAllowance = sub96(spenderAllowance, amount, "Comfi::transferFrom: transfer amount exceeds spender allowance");
            allowances[src][spender] = newAllowance;

            emit Approval(src, spender, newAllowance);
        }

        _transferTokens(src, dst, amount);
        return true;
    }


// RZ Mint Slots

function availableRZMintSlots() public view returns (uint256) {

        uint256 availableMintSlots = 0;
        if (ribonzContract.balanceOf(msg.sender) == 0) {
            // If not holding RIBONZ genesis can't get an RZ mint slot
            return 0;
        }

        uint256[] memory stTokensOfOwner = spacetimeContract.tokensOfOwner(msg.sender);

        for (uint256 i= 0; i<stTokensOfOwner.length; i++) {
            if (spaceTimeMintSlots[stTokensOfOwner[i]] == 0) {
                availableMintSlots++;
            }
        }

        return availableMintSlots;
    }


//
// Mint a voygagerz with special promo for RIBONZ Genesis  + Spacetime holders
//

function mintVoyagerzWithRZ(uint256 num_to_mint) public payable virtual nonReentrant {
        // Precondition checks here
        uint256 availableSlots = availableRZMintSlots();

        require(availableSlots >= num_to_mint, "Need to hold RIBONZ: Genesis and more RIBONZ: Spacetime than mint count");
        console.log('RZ MINT SLOTS', availableSlots);

        // Each special offer mint 'consumes' a spacetime special offer slot
        uint256[] memory stTokensOfOwner = spacetimeContract.tokensOfOwner(msg.sender);

        // Do the mint at discount price!
        mintVoyagerzInternal(num_to_mint, mintPriceSpecialOfferForRZHolders);

        uint256 mintSlotsToConsume = num_to_mint;

        //
        // Consume the mint slots
        //
        for (uint256 i= 0; i<stTokensOfOwner.length; i++) {
            // Eat up a slot
            if (mintSlotsToConsume > 0 && (spaceTimeMintSlots[stTokensOfOwner[i]] == 0)) {
                spaceTimeMintSlots[stTokensOfOwner[i]] = 1;
                mintSlotsToConsume--;
                console.log('ST Mint Slot CONSUME', stTokensOfOwner[i]);
            }
        }
    }


/**
 * @dev Returns true if `account` is a contract.
 *
 * [IMPORTANT]
 * ====
 * It is unsafe to assume that an address for which this function returns
 * false is an externally-owned account (EOA) and not a contract.
 *
 * Among others, `isContract` will return false for the following
 * types of addresses:
 *
 *  - an externally-owned account
 *  - a contract in construction
 *  - an address where a contract will be created
 *  - an address where a contract lived, but was destroyed
 * ====
 *
 * [IMPORTANT]
 * ====
 * You shouldn't rely on `isContract` to protect against flash loan attacks!
 *
 * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
 * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
 * constructor.
 * ====
 */

function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }


/**
 * @dev 1. Mints an mAsset and then deposits to SAVE
 * @param _bAsset       bAsset address
 * @param _amt          Amount of bAsset to mint with
 * @param _stake        Add the imUSD to the Savings Vault?
 */

function saveViaMint(address _bAsset, uint256 _amt, bool _stake) external {
        // 1. Get the input bAsset
        IERC20(_bAsset).transferFrom(msg.sender, address(this), _amt);
        // 2. Mint
        IMasset mAsset_ = IMasset(mAsset);
        uint256 massetsMinted = mAsset_.mint(_bAsset, _amt);
        // 3. Mint imUSD and optionally stake in vault
        _saveAndStake(massetsMinted, _stake);
    }


/**
 * @dev 2. Buys mUSD on Curve, mints imUSD and optionally deposits to the vault
 * @param _input         bAsset to sell
 * @param _curvePosition Index of the bAsset in the Curve pool
 * @param _minOutCrv     Min amount of mUSD to receive
 * @param _amountIn      Input asset amount
 * @param _stake         Add the imUSD to the Savings Vault?
 */

function saveViaCurve(
        address _input,
        int128 _curvePosition,
        uint256 _amountIn,
        uint256 _minOutCrv,
        bool _stake
    ) external {
        // 1. Get the input asset
        IERC20(_input).transferFrom(msg.sender, address(this), _amountIn);
        // 2. Purchase mUSD
        uint256 purchased = curve.exchange_underlying(_curvePosition, 0, _amountIn, _minOutCrv);
        // 3. Mint imUSD and optionally stake in vault
        _saveAndStake(purchased, _stake);
    }


/**
 * @dev 3. Buys a bAsset on Uniswap with ETH then mUSD on Curve
 * @param _amountOutMin  bAsset to sell
 * @param _path          Sell path on Uniswap (e.g. [WETH, DAI])
 * @param _curvePosition Index of the bAsset in the Curve pool
 * @param _minOutCrv     Min amount of mUSD to receive
 * @param _stake         Add the imUSD to the Savings Vault?
 */

function saveViaUniswapETH(
        uint256 _amountOutMin,
        address[] calldata _path,
        int128 _curvePosition,
        uint256 _minOutCrv,
        bool _stake
    ) external payable {
        // 1. Get the bAsset
        uint[] memory amounts = uniswap.swapExactETHForTokens.value(msg.value)(
            _amountOutMin,
            _path,
            address(this),
            now + 1000
        );
        // 2. Purchase mUSD
        uint256 purchased = curve.exchange_underlying(_curvePosition, 0, amounts[amounts.length-1], _minOutCrv);
        // 3. Mint imUSD and optionally stake in vault
        _saveAndStake(purchased, _stake);
    }


/**
 * @notice Transfers amount amount of an _id from the _from address to the _to address specified
 * @param _from    Source address
 * @param _to      Target address
 * @param _id      ID of the token type
 * @param _amount  Transfered amount
 * @param _data    Additional data with no specified format, sent in call to `_to`
 */

function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data)
    public
  {
    require((msg.sender == _from) || operators[_from][msg.sender], "ERC1155#safeTransferFrom: INVALID_OPERATOR");
    require(_to != address(0),"ERC1155#safeTransferFrom: INVALID_RECIPIENT");
    // require(_amount >= balances[_from][_id]) is not necessary since checked with safemath operations

    _safeTransferFrom(_from, _to, _id, _amount);
    _callonERC1155Received(_from, _to, _id, _amount, _data);
  }


/**
 * @notice Send multiple types of Tokens from the _from address to the _to address (with safety call)
 * @param _from     Source addresses
 * @param _to       Target addresses
 * @param _ids      IDs of each token type
 * @param _amounts  Transfer amounts per token type
 * @param _data     Additional data with no specified format, sent in call to `_to`
 */

function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _amounts, bytes memory _data)
    public
  {
    // Requirements
    require((msg.sender == _from) || operators[_from][msg.sender], "ERC1155#safeBatchTransferFrom: INVALID_OPERATOR");
    require(_to != address(0), "ERC1155#safeBatchTransferFrom: INVALID_RECIPIENT");

    _safeBatchTransferFrom(_from, _to, _ids, _amounts);
    _callonERC1155BatchReceived(_from, _to, _ids, _amounts, _data);
  }


/**
 * @notice Transfers amount amount of an _id from the _from address to the _to address specified
 * @param _from    Source address
 * @param _to      Target address
 * @param _id      ID of the token type
 * @param _amount  Transfered amount
 */

function _safeTransferFrom(address _from, address _to, uint256 _id, uint256 _amount)
    internal
  {
    // Update balances
    balances[_from][_id] = balances[_from][_id].sub(_amount); // Subtract amount
    balances[_to][_id] = balances[_to][_id].add(_amount);     // Add amount

    // Emit event
    emit TransferSingle(msg.sender, _from, _to, _id, _amount);
  }


/**
 * @notice Verifies if receiver is contract and if so, calls (_to).onERC1155Received(...)
 */

function _callonERC1155Received(address _from, address _to, uint256 _id, uint256 _amount, bytes memory _data)
    internal
  {
    // Check if recipient is contract
    if (_to.isContract()) {
      bytes4 retval = IERC1155TokenReceiver(_to).onERC1155Received(msg.sender, _from, _id, _amount, _data);
      require(retval == ERC1155_RECEIVED_VALUE, "ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE");
    }
  }


/**
 * @dev Minting for whitelisted addresses
 *
 * Requirements:
 *
 * Contract must be unpaused
 * The presale must be going on
 * The caller must request less than the max by address authorized
 * The amount of token must be superior to 0
 * The supply must not be empty
 * The price must be correct
 *
 * @param amountToMint the number of token to mint
 * @param merkleProof for the wallet address
 *
 */

function whitlistedMinting (uint amountToMint, bytes32[] calldata merkleProof ) external payable isWhitelisted(merkleProof) {
        require (!_paused, "Contract paused");
        require (_presale, "Presale over!");
        require (amountToMint + balanceOf(msg.sender) <= _maxNftByWallet, "Requet too much for a wallet");
        require (amountToMint > 0, "Error in the amount requested");
        require (amountToMint + totalSupply() <= _maxSupply - _reserved, "Request superior max Supply");
        require (msg.value >= _price*amountToMint, "Insuficient funds");
        _mintNFT(amountToMint);
    }


/**
 * @dev Minting for the public sale
 *
 * Requirements:
 *
 * The presale must be over
 * The caller must request less than the max by address authorized
 * The amount of token must be superior to 0
 * The supply must not be empty
 * The price must be correct
 *
 * @param amountToMint the number of token to mint
 *
 */

function publicMint(uint amountToMint) external payable{
        require (!_paused, "Contract paused");
        require (!_presale, "Presale on");
        require (amountToMint + balanceOf(msg.sender) <= _maxNftByWallet, "Requet too much for a wallet");
        require (amountToMint > 0, "Error in the amount requested");
        require (amountToMint + totalSupply() <= _maxSupply - _reserved, "Request superior max Supply");
        require (msg.value >= _price*amountToMint, "Insuficient funds");
        _mintNFT(amountToMint);
    }


/**
 * @notice Get the balance of multiple account/token pairs
 * @param _owners The addresses of the token holders
 * @param _ids    ID of the Tokens
 * @return        The _owner's balance of the Token types requested (i.e. balance for each (owner, id) pair)
 */

function balanceOfBatch(address[] memory _owners, uint256[] memory _ids)
    public view returns (uint256[] memory)
  {
    require(_owners.length == _ids.length, "ERC1155#balanceOfBatch: INVALID_ARRAY_LENGTH");

    // Variables
    uint256[] memory batchBalances = new uint256[](_owners.length);

    // Iterate over each owner and token ID
    for (uint256 i = 0; i < _owners.length; i++) {
      batchBalances[i] = balances[_owners[i]][_ids[i]];
    }

    return batchBalances;
  }


/**
 * @dev Give away attribution
 *
 * Requirements:
 *
 * The recipient must be different than 0
 * The amount of token requested must be within the reverse
 * The amount requested must be supperior to 0
 *
 */

function giveAway (address to, uint amountToMint) external onlyOwner{
         require (to != address(0), "address 0 requested");
         require (amountToMint <= _reserved, "You requested more than the reserved token");
         require (amountToMint > 0, "Amount Issue");

        uint currentSupply = totalSupply();
        _reserved = _reserved - amountToMint;
         for (uint i; i < amountToMint ; i++) {
            _safeMint(to, currentSupply + i);
        }
     }


/**
 * @dev Return an array of token Id owned by `owner`
 */

function getWallet(address _owner) public view returns(uint [] memory){
        uint numberOwned = balanceOf(_owner);
        uint [] memory idItems = new uint[](numberOwned);

        for (uint i = 0; i < numberOwned; i++){
            idItems[i] = tokenOfOwnerByIndex(_owner,i);
        }
        return idItems;
    }


// https://ethereum.stackexchange.com/questions/19341/address-send-vs-address-transfer-best-practice-usage

function approve(
        address payable _destination,
        uint256 _amount
    ) public isMember sufficient(_amount) returns (bool) {
        Approval storage approval  = approvals[_destination][_amount]; // Create new project

        if (!approval.coinciedeParties[msg.sender]) {
            approval.coinciedeParties[msg.sender] = true;
            approval.coincieded += 1;

            emit ConfirmationReceived(msg.sender, _destination, ETHER_ADDRESS, _amount);

            if (
                approval.coincieded >= signatureMinThreshold &&
                !approval.transfered
            ) {
                approval.transfered = true;

                uint256 _amountToWithhold = _amount.mul(serviceFeeMicro).div(1000000);
                uint256 _amountToRelease = _amount.sub(_amountToWithhold);

                _destination.transfer(_amountToRelease);    // Release funds
                serviceAddress.transfer(_amountToWithhold); // Take service margin

                emit ConsensusAchived(_destination, ETHER_ADDRESS, _amount);
            }

            return true;
        } else {
            // Raise will eat rest of gas. Lets not waist it. Just record this approval instead
            return false;
        }
    }


// @notice owner can start the sale by transferring in required amount of MYB
// @dev the start time is used to determine which day the sale is on (day 0 = first day)

function startSale(uint _timestamp)
  external
  onlyOwner
  returns (bool){
    require(start == 0, 'Already started');
    require(_timestamp >= now  && _timestamp.sub(now) < 2592000, 'Start time not in range');
    uint saleAmount = tokensPerDay.mul(365);
    require(mybToken.transferFrom(msg.sender, address(this), saleAmount));
    start = _timestamp;
    emit LogSaleStarted(msg.sender, mybitFoundation, developmentFund, saleAmount, _timestamp);
    return true;
  }


// @notice Send an index of days and your payment will be divided equally among them
// @dev WEI sent must divide equally into number of days.

function batchFund(uint16[] _day)
  payable
  external
  returns (bool) {
    require(_day.length <= 50);       // Limit to 50 days to avoid exceeding blocklimit
    require(msg.value >= _day.length);   // need at least 1 wei per day
    uint256 amountPerDay = msg.value.div(_day.length);
    assert (amountPerDay.mul(_day.length) == msg.value);   // Don't allow any rounding error
    for (uint8 i = 0; i < _day.length; i++){
      require(addContribution(msg.sender, amountPerDay, _day[i]));
    }
    return true;
  }


// @notice Updates claimableTokens, sends all wei to the token holder

function withdraw(uint16 _day)
  external
  returns (bool) {
      require(dayFinished(_day), "day has not finished funding");
      Day storage thisDay = day[_day];
      uint256 amount = getTokensOwed(msg.sender, _day);
      delete thisDay.weiContributed[msg.sender];
      mybToken.transfer(msg.sender, amount);
      emit LogTokensCollected(msg.sender, amount, _day);
      return true;
  }


// @notice Updates claimableTokens, sends all tokens to contributor from previous days
// @param (uint16[]) _day, list of token sale days msg.sender contributed wei towards

function batchWithdraw(uint16[] _day)
  external
  returns (bool) {
    uint256 amount;
    require(_day.length <= 50);     // Limit to 50 days to avoid exceeding blocklimit
    for (uint8 i = 0; i < _day.length; i++){
      require(dayFinished(_day[i]));
      uint256 amountToAdd = getTokensOwed(msg.sender, _day[i]);
      amount = amount.add(amountToAdd);
      delete day[_day[i]].weiContributed[msg.sender];
      emit LogTokensCollected(msg.sender, amountToAdd, _day[i]);
    }
    mybToken.transfer(msg.sender, amount);
    return true;
  }


// @notice updates ledger with the contribution from _investor
// @param (address) _investor: The sender of WEI to the contract
// @param (uint) _amount: The amount of WEI to add to _day
// @param (uint16) _day: The day to fund

function addContribution(address _investor, uint _amount, uint16 _day)
  internal
  returns (bool) {
    require(_amount > 0, "must send ether with the call");
    require(duringSale(_day), "day is not during the sale");
    require(!dayFinished(_day), "day has already finished");
    Day storage today = day[_day];
    today.totalWeiContributed = today.totalWeiContributed.add(_amount);
    today.weiContributed[_investor] = today.weiContributed[_investor].add(_amount);
    emit LogTokensPurchased(_investor, _amount, _day);
    return true;
  }


// @notice gets the total amount of mybit owed to the contributor
// @dev this function doesn't check for duplicate days. Output may not reflect actual amount owed if this happens.

function getTotalTokensOwed(address _contributor, uint16[] _days)
  public
  view
  returns (uint256 amount) {
    require(_days.length < 100);          // Limit to 100 days to avoid exceeding block gas limit
    for (uint16 i = 0; i < _days.length; i++){
      amount = amount.add(getTokensOwed(_contributor, _days[i]));
    }
    return amount;
  }


// @notice returns true if _day is finished

function dayFinished(uint16 _day)
  public
  view
  returns (bool) {
    if (now <= start) { return false; }   // hasn't yet reached first day, so cannot be finished
    return dayFor(now) > _day;
  }


/**
 * @dev Implementation of IERC777Recipient.
 */

function tokensReceived(
        address /*operator*/,
        address from,
        address to,
        uint256 amount,
        bytes calldata userData,
        bytes calldata /*operatorData*/
    ) external override {
        address _tokenAddress = msg.sender;
        require(supportedTokens.contains(_tokenAddress), "caller is not a supported ERC777 token!");
        require(to == address(this), "Token receiver is not this contract");
        if (userData.length > 0) {
            require(amount > 0, "Token amount must be greater than zero!");
            (bytes32 tag, string memory _destinationAddress) = abi.decode(userData, (bytes32, string));
            require(tag == keccak256("ERC777-pegIn"), "Invalid tag for automatic pegIn on ERC777 send");
            emit PegIn(_tokenAddress, from, amount, _destinationAddress);
        }
    }


// Called to reward current KOTH winner and start new game

function rewardKoth() public {
        if (msg.sender == feeAddress && lastBlock > 0 && block.number > lastBlock) {
            uint fee = pot / 20; // 5%
            KothWin(gameId, betId, koth, highestBet, pot, fee, firstBlock, lastBlock);

            uint netPot = pot - fee;
            address winner = koth;
            resetKoth();
            winner.transfer(netPot);

            // Make sure we never go below minPot
            if (this.balance - fee >= minPot) {
                feeAddress.transfer(fee);
            }
        }
    }


//a new 'block' to be mined

function _startNewMiningEpoch() internal
      {
      //if max supply for the era will be exceeded next reward round then enter the new era before that happens
      //4 is the final reward era, almost all tokens minted
      //once the final era is reached, more tokens will not be given out because the assert function

      if( tokensMinted.add(getMiningReward()) > maxSupplyForEra && rewardEra < 12)
         {
         rewardEra = rewardEra + 1;
         maxSupplyForEra = eraVsMaxSupplyFactor[rewardEra].mul(1000000000) * 10**uint(decimals);
         }

      epochCount = epochCount.add(1);

      //every so often, readjust difficulty. Dont readjust when deploying
      if(epochCount % _BLOCKS_PER_READJUSTMENT == 0)
         {
         _reAdjustDifficulty();
         }

      //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks
      //do this last since this is a protection mechanism in the mint() function
      challengeNumber = block.blockhash(block.number - 1);
      }


//10,000,000,000 coins total
//reward begins at 100,000 and is cut in half every reward era (as tokens are mined)

function getMiningReward() public constant returns (uint)
      {
      //once we get half way thru the coins, only get 50,000 per block
      //every reward era, the reward amount halves.
      return (100000 * 10**uint(decimals) ).div(5).mul(eraVsMaxSupplyFactor[rewardEra]) ;
      }


/// @notice Create an option order for a given NFT
/// @param optionPrice the price of the option
/// @param strikePrice the strike price of the option
/// @param settlementTimestamp the option maturity timestamp
/// @param nftId the token ID of the NFT relevant of this order
/// @param nftContract the address of the NFT contract
/// @param optionType the type of the option (CallBuy, CallSale, PutBuy or PutSale)

function makeOrder(
        uint256 optionPrice,
        uint256 strikePrice,
        uint256 settlementTimestamp,
        uint256 nftId,
        address nftContract,
        NFTeacket.OptionType optionType
    ) external payable onlyWhenNFTeacketSet {
        require(
            settlementTimestamp > block.timestamp,
            "NFTea: Incorrect timestamp"
        );

        if (optionType == NFTeacket.OptionType.CallBuy) {
            _requireEthSent(optionPrice);
        } else if (optionType == NFTeacket.OptionType.CallSale) {
            _requireEthSent(0);
            _lockNft(nftId, nftContract);
        } else if (optionType == NFTeacket.OptionType.PutBuy) {
            _requireEthSent(optionPrice);
        } else {
            // OptionType.Putsale
            _requireEthSent(strikePrice);
        }

        NFTeacket.OptionDataMaker memory data = NFTeacket.OptionDataMaker({
            price: optionPrice,
            strike: strikePrice,
            settlementTimestamp: settlementTimestamp,
            nftId: nftId,
            nftContract: nftContract,
            takerTicketId: 0,
            optionType: optionType
        });

        uint256 makerTicketId = NFTeacket(nfteacket).mintMakerTicket(
            msg.sender,
            data
        );

        emit OrderCreated(makerTicketId, data);
    }


/// @notice Cancel a non filled order
/// @param makerTicketId the ticket ID associated with the order

function cancelOrder(uint256 makerTicketId) external onlyWhenNFTeacketSet {
        NFTeacket _nfteacket = NFTeacket(nfteacket);

        // Check the seender is the maker
        _requireTicketOwner(msg.sender, makerTicketId);

        NFTeacket.OptionDataMaker memory optionData = _nfteacket
            .ticketIdToOptionDataMaker(makerTicketId);

        require(optionData.takerTicketId == 0, "NFTea: Order already filled");

        if (optionData.optionType == NFTeacket.OptionType.CallBuy) {
            _transferEth(msg.sender, optionData.price);
        } else if (optionData.optionType == NFTeacket.OptionType.CallSale) {
            _transferNft(
                address(this),
                msg.sender,
                optionData.nftId,
                optionData.nftContract
            );
        } else if (optionData.optionType == NFTeacket.OptionType.PutBuy) {
            _transferEth(msg.sender, optionData.price);
        } else {
            // OptionType.Putsale
            _transferEth(msg.sender, optionData.strike);
        }

        _nfteacket.burnTicket(makerTicketId);

        emit OrderCancelled(makerTicketId);
    }


/// @dev When a trader wants to sell a call, he has to lock his NFT
/// until maturity
/// @param nftId the token ID of the NFT to lock
/// @param nftContractAddress address of the NFT contract

function _lockNft(uint256 nftId, address nftContractAddress) private {
        IERC721 nftContract = IERC721(nftContractAddress);
        address owner = nftContract.ownerOf(nftId);

        require(owner == msg.sender, "NFTea : Not nft owner");

        require(
            address(this) == owner ||
                nftContract.getApproved(nftId) == address(this) ||
                nftContract.isApprovedForAll(owner, address(this)),
            "NFTea : Contract not approved, can't lock nft"
        );

        // Lock the nft by transfering it to the contract
        nftContract.transferFrom(msg.sender, address(this), nftId);
    }


/**
 * @dev See {ERC20-_beforeTokenTransfer}.
 *
 * Requirements:
 *
 * - minted tokens must not cause the total supply to go over the cap.
 */

function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override {
        super._beforeTokenTransfer(from, to, amount);

        if (from == address(0)) { // When minting tokens
            require(totalSupply().add(amount) <= cap(), "ERC20Capped: cap exceeded");
        }
    }


/* mapping (uint => address) public wikiToOwner;
 mapping (address => uint) ownerWikiCount; */

function createWikiPage(string _title, string _articleHash, string _imageHash, uint _price) public onlyOwner returns (uint) {
    uint id = wikiPages.push(WikiPage(_title, _articleHash, _imageHash, _price)) - 1;
    /* tokenOwner[id] = msg.sender;
    ownedTokensCount[msg.sender]++; */
    _ownMint(id);
  }


/**
 * @return The result of safely multiplying x and y, interpreting the operands
 * as fixed-point decimals of the specified precision unit.
 *
 * @dev The operands should be in the form of a the specified unit factor which will be
 * divided out after the product of x and y is evaluated, so that product must be
 * less than 2**256.
 *
 * Unlike multiplyDecimal, this function rounds the result to the nearest increment.
 * Rounding is useful when you need to retain fidelity for small decimal numbers
 * (eg. small fractions or percentages).
 */

function _multiplyDecimalRound(uint x, uint y, uint precisionUnit)
        private
        pure
        returns (uint)
    {
        /* Divide by UNIT to remove the extra factor introduced by the product. */
        uint quotientTimesTen = x.mul(y) / (precisionUnit / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }


/**
 * @return The result of safely dividing x and y. The return value is as a rounded
 * decimal in the precision unit specified in the parameter.
 *
 * @dev y is divided after the product of x and the specified precision unit
 * is evaluated, so the product of x and the specified precision unit must
 * be less than 2**256. The result is rounded to the nearest increment.
 */

function _divideDecimalRound(uint x, uint y, uint precisionUnit)
        private
        pure
        returns (uint)
    {
        uint resultTimesTen = x.mul(precisionUnit * 10).div(y);

        if (resultTimesTen % 10 >= 5) {
            resultTimesTen += 10;
        }

        return resultTimesTen / 10;
    }


/**
 * @dev Convert a high precision decimal to a standard decimal representation.
 */

function preciseDecimalToDecimal(uint i)
        internal
        pure
        returns (uint)
    {
        uint quotientTimesTen = i / (UNIT_TO_HIGH_PRECISION_CONVERSION_FACTOR / 10);

        if (quotientTimesTen % 10 >= 5) {
            quotientTimesTen += 10;
        }

        return quotientTimesTen / 10;
    }


/**
 * @dev Perform an ERC20 token transferFrom. Designed to be called by transferFrom functions
 * possessing the optionalProxy or optionalProxy modifiers.
 */

function _transferFrom_byProxy(address sender, address from, address to, uint value)
        internal
        returns (bool)
    {
        /* Insufficient allowance will be handled by the safe subtraction. */
        tokenState.setAllowance(from, sender, tokenState.allowance(from, sender).sub(value));
        return _internalTransfer(from, to, value);
    }


/**
 * @dev Uses "exponentiation by squaring" algorithm where cost is 0(logN)
 * vs 0(N) for naive repeated multiplication.
 * Calculates x^n with x as fixed-point and n as regular unsigned int.
 * Calculates to 18 digits of precision with SafeDecimalMath.unit()
 */

function powDecimal(uint x, uint n)
        internal
        pure
        returns (uint)
    {
        // https://mpark.github.io/programming/2014/08/18/exponentiation-by-squaring/

        uint result = SafeDecimalMath.unit();
        while (n > 0) {
            if (n % 2 != 0) {
                result = result.multiplyDecimal(x);
            }
            x = x.multiplyDecimal(x);
            n /= 2;
        }
        return result;
    }


/**
 * @notice ERC20 transferFrom function
 */

function transferFrom(address from, address to, uint value)
        public
        optionalProxy
        returns (bool)
    {
        // Skip allowance update in case of infinite allowance
        if (tokenState.allowance(from, messageSender) != uint(-1)) {
            // Reduce the allowance by the amount we're transferring.
            // The safeSub call will handle an insufficient allowance.
            tokenState.setAllowance(from, messageSender, tokenState.allowance(from, messageSender).sub(value));
        }

        return super._internalTransfer(from, to, value);
    }


/**
 * @return A unit amount of decaying inflationary supply from the INITIAL_WEEKLY_SUPPLY
 * @dev New token supply reduces by the decay rate each week calculated as supply = INITIAL_WEEKLY_SUPPLY * ()
 */

function tokenDecaySupplyForWeek(uint counter)
        public
        pure
        returns (uint)
    {
        // Apply exponential decay function to number of weeks since
        // start of inflation smoothing to calculate diminishing supply for the week.
        uint effectiveDecay = (SafeDecimalMath.unit().sub(DECAY_RATE)).powDecimal(counter);
        uint supplyForWeek = INITIAL_WEEKLY_SUPPLY.multiplyDecimal(effectiveDecay);

        return supplyForWeek;
    }


/**
 * @return A unit amount of terminal inflation supply
 * @dev Weekly compound rate based on number of weeks
 */

function terminalInflationSupply(uint totalSupply, uint numOfWeeks)
        public
        pure
        returns (uint)
    {
        // rate = (1 + weekly rate) ^ num of weeks
        uint effectiveCompoundRate = SafeDecimalMath.unit().add(TERMINAL_SUPPLY_RATE_ANNUAL.div(52)).powDecimal(numOfWeeks);

        // return Supply * (effectiveRate - 1) for extra supply to issue based on number of weeks
        return totalSupply.multiplyDecimal(effectiveCompoundRate.sub(SafeDecimalMath.unit()));
    }


/**
 * @dev Take timeDiff in seconds (Dividend) and MINT_PERIOD_DURATION as (Divisor)
 * @return Calculate the numberOfWeeks since last mint rounded down to 1 week
 */

function weeksSinceLastIssuance()
        public
        view
        returns (uint)
    {
        // Get weeks since lastMintEvent
        // If lastMintEvent not set or 0, then start from inflation start date.
        uint timeDiff = lastMintEvent > 0 ? now.sub(lastMintEvent) : now.sub(INFLATION_START_DATE);
        return timeDiff.div(MINT_PERIOD_DURATION);
    }


/**
 * @notice Record the mint event from Synthetix by incrementing the inflation
 * week counter for the number of weeks minted (probabaly always 1)
 * and store the time of the event.
 * @param supplyMinted the amount of SNX the total supply was inflated by.
 * */

function recordMintEvent(uint supplyMinted)
        external
        onlySynthetix
        returns (bool)
    {
        uint numberOfWeeksIssued = weeksSinceLastIssuance();

        // add number of weeks minted to weekCounter
        weekCounter = weekCounter.add(numberOfWeeksIssued);

        // Update mint event to latest week issued (start date + number of weeks issued * seconds in week)
        // 1 day time buffer is added so inflation is minted after feePeriod closes
        lastMintEvent = INFLATION_START_DATE.add(weekCounter.mul(MINT_PERIOD_DURATION)).add(MINT_BUFFER);

        emit SupplyMinted(supplyMinted, numberOfWeeksIssued, lastMintEvent, now);
        return true;
    }


/**
 * @notice Retrieve the last update time for a list of currencies
 */

function lastRateUpdateTimesForCurrencies(bytes32[] currencyKeys)
        public
        view
        returns (uint[])
    {
        uint[] memory lastUpdateTimes = new uint[](currencyKeys.length);

        for (uint i = 0; i < currencyKeys.length; i++) {
            lastUpdateTimes[i] = lastRateUpdateTimes(currencyKeys[i]);
        }

        return lastUpdateTimes;
    }


/**
 * @notice Remove an inverse price for the currency key
 * @param currencyKey The currency to remove inverse pricing for
 */

function removeInversePricing(bytes32 currencyKey) external onlyOwner
    {
        require(inversePricing[currencyKey].entryPoint > 0, "No inverted price exists");

        inversePricing[currencyKey].entryPoint = 0;
        inversePricing[currencyKey].upperLimit = 0;
        inversePricing[currencyKey].lowerLimit = 0;
        inversePricing[currencyKey].frozen = false;

        // now remove inverted key from array
        bool wasRemoved = removeFromArray(currencyKey, invertedKeys);

        if (wasRemoved) {
            emit InversePriceConfigured(currencyKey, 0, 0, 0);
        }
    }


/**
 * @notice Add a pricing aggregator for the given key. Note: existing aggregators may be overridden.
 * @param currencyKey The currency key to add an aggregator for
 */

function addAggregator(bytes32 currencyKey, address aggregatorAddress) external onlyOwner {
        AggregatorInterface aggregator = AggregatorInterface(aggregatorAddress);
        require(aggregator.latestTimestamp() >= 0, "Given Aggregator is invalid");
        if (aggregators[currencyKey] == address(0)) {
            aggregatorKeys.push(currencyKey);
        }
        aggregators[currencyKey] = aggregator;
        emit AggregatorAdded(currencyKey, aggregator);
    }


/**
 * @notice Remove a single value from an array by iterating through until it is found.
 * @param entry The entry to find
 * @param array The array to mutate
 * @return bool Whether or not the entry was found and removed
 */

function removeFromArray(bytes32 entry, bytes32[] storage array) internal returns (bool) {
        for (uint i = 0; i < array.length; i++) {
            if (array[i] == entry) {
                delete array[i];

                // Copy the last key into the place of the one we just deleted
                // If there's only one key, this is array[0] = array[0].
                // If we're deleting the last one, it's also a NOOP in the same way.
                array[i] = array[array.length - 1];

                // Decrease the size of the array by one.
                array.length--;

                return true;
            }
        }
        return false;
    }


/**
 * @notice Remove a pricing aggregator for the given key
 * @param currencyKey THe currency key to remove an aggregator for
 */

function removeAggregator(bytes32 currencyKey) external onlyOwner {
        address aggregator = aggregators[currencyKey];
        require(aggregator != address(0), "No aggregator exists for key");
        delete aggregators[currencyKey];

        bool wasRemoved = removeFromArray(currencyKey, aggregatorKeys);

        if (wasRemoved) {
            emit AggregatorRemoved(currencyKey, aggregator);
        }
    }


/**
 * @notice A function that lets you easily convert an amount in a source currency to an amount in the destination currency
 * @param sourceCurrencyKey The currency the amount is specified in
 * @param sourceAmount The source amount, specified in UNIT base
 * @param destinationCurrencyKey The destination currency
 */

function effectiveValue(bytes32 sourceCurrencyKey, uint sourceAmount, bytes32 destinationCurrencyKey)
        public
        view
        rateNotStale(sourceCurrencyKey)
        rateNotStale(destinationCurrencyKey)
        returns (uint)
    {
        // If there's no change in the currency, then just return the amount they gave us
        if (sourceCurrencyKey == destinationCurrencyKey) return sourceAmount;

        // Calculate the effective value by going from source -> USD -> destination
        return sourceAmount.multiplyDecimalRound(rateForCurrency(sourceCurrencyKey))
            .divideDecimalRound(rateForCurrency(destinationCurrencyKey));
    }


/**
 * @notice Retrieve the rates for a list of currencies
 */

function ratesForCurrencies(bytes32[] currencyKeys)
        public
        view
        returns (uint[])
    {
        uint[] memory _localRates = new uint[](currencyKeys.length);

        for (uint i = 0; i < currencyKeys.length; i++) {
            _localRates[i] = rates(currencyKeys[i]);
        }

        return _localRates;
    }


/**
 * @notice Retrieve the rates and isAnyStale for a list of currencies
 */

function ratesAndStaleForCurrencies(bytes32[] currencyKeys)
        public
        view
        returns (uint[], bool)
    {
        uint[] memory _localRates = new uint[](currencyKeys.length);

        bool anyRateStale = false;
        uint period = rateStalePeriod;
        for (uint i = 0; i < currencyKeys.length; i++) {
            RateAndUpdatedTime memory rateAndUpdateTime = getRateAndUpdatedTime(currencyKeys[i]);
            _localRates[i] = uint256(rateAndUpdateTime.rate);
            if (!anyRateStale) {
                anyRateStale = (currencyKeys[i] != "sUSD" && uint256(rateAndUpdateTime.time).add(period) < now);
            }
        }

        return (_localRates, anyRateStale);
    }


/**
 * @notice Check if any of the currency rates passed in haven't been updated for longer than the stale period.
 */

function anyRateIsStale(bytes32[] currencyKeys)
        external
        view
        returns (bool)
    {
        // Loop through each key and check whether the data point is stale.
        uint256 i = 0;

        while (i < currencyKeys.length) {
            // sUSD is a special case and is never false
            if (currencyKeys[i] != "sUSD" && lastRateUpdateTimes(currencyKeys[i]).add(rateStalePeriod) < now) {
                return true;
            }
            i += 1;
        }

        return false;
    }


/**
 * @notice Add an associated Synth contract to the Synthetix system
 * @dev Only the contract owner may call this.
 */

function addSynth(Synth synth)
        external
        optionalProxy_onlyOwner
    {
        bytes32 currencyKey = synth.currencyKey();

        require(synths[currencyKey] == Synth(0), "Synth already exists");
        require(synthsByAddress[synth] == bytes32(0), "Synth address already exists");

        availableSynths.push(synth);
        synths[currencyKey] = synth;
        synthsByAddress[synth] = currencyKey;
    }


/**
 * @notice Total amount of synths issued by the system, priced in currencyKey
 * @param currencyKey The currency to value the synths in
 */

function totalIssuedSynths(bytes32 currencyKey)
        public
        view
        returns (uint)
    {
        uint total = 0;
        uint currencyRate = exchangeRates.rateForCurrency(currencyKey);

        (uint[] memory rates, bool anyRateStale) = exchangeRates.ratesAndStaleForCurrencies(availableCurrencyKeys());
        require(!anyRateStale, "Rates are stale");

        for (uint i = 0; i < availableSynths.length; i++) {
            // What's the total issued value of that synth in the destination currency?
            // Note: We're not using our effectiveValue function because we don't want to go get the
            //       rate for the destination currency and check if it's stale repeatedly on every
            //       iteration of the loop
            uint synthValue = availableSynths[i].totalSupply()
                .multiplyDecimalRound(rates[i]);
            total = total.add(synthValue);
        }

        return total.divideDecimalRound(currencyRate);
    }


/**
 * @notice Determine the effective fee rate for the exchange, taking into considering swing trading
 */

function feeRateForExchange(bytes32 sourceCurrencyKey, bytes32 destinationCurrencyKey)
        public
        view
        returns (uint)
    {
        // Get the base exchange fee rate
        uint exchangeFeeRate = feePool.exchangeFeeRate();

        uint multiplier = 1;

        // Is this a swing trade? I.e. long to short or vice versa, excluding when going into or out of sUSD.
        // Note: this assumes shorts begin with 'i' and longs with 's'.
        if (
            (sourceCurrencyKey[0] == 0x73 && sourceCurrencyKey != "sUSD" && destinationCurrencyKey[0] == 0x69) ||
            (sourceCurrencyKey[0] == 0x69 && destinationCurrencyKey != "sUSD" && destinationCurrencyKey[0] == 0x73)
        ) {
            // If so then double the exchange fee multipler
            multiplier = 2;
        }

        return exchangeFeeRate.mul(multiplier);
    }


/**
 * @notice ERC20 transfer function.
 */

function transfer(address to, uint value)
        public
        optionalProxy
        returns (bool)
    {
        // Ensure they're not trying to exceed their staked SNX amount
        require(value <= transferableSynthetix(messageSender), "Cannot transfer staked or escrowed SNX");

        // Perform the transfer: if there is a problem an exception will be thrown in this call.
        _transfer_byProxy(messageSender, to, value);

        return true;
    }


// mint the reserve tokens for later airdroping

function mintReserve( uint _mType, address _to, string memory _uri ) public onlyRole(MINTER_ROLE) {

        // Mint reserve supply
        uint quantity = _reservedSupply[_mType];
        require( quantity > 0, "M10: already minted" );

        _mintTokens( _mType, quantity, _to, _uri, true );

        // erase the reserve
        _reservedSupply[_mType] = 0;
    }


/**
 * @notice Function that allows you to exchange synths you hold in one flavour for another.
 * @param sourceCurrencyKey The source currency you wish to exchange from
 * @param sourceAmount The amount, specified in UNIT of source currency you wish to exchange
 * @param destinationCurrencyKey The destination currency you wish to obtain.
 * @return Boolean that indicates whether the transfer succeeded or failed.
 */

function exchange(bytes32 sourceCurrencyKey, uint sourceAmount, bytes32 destinationCurrencyKey)
        external
        optionalProxy
        // Note: We don't need to insist on non-stale rates because effectiveValue will do it for us.
        returns (bool)
    {
        require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth");
        require(sourceAmount > 0, "Zero amount");

        // verify gas price limit
        validateGasPrice(tx.gasprice);

        //  If the oracle has set protectionCircuit to true then burn the synths
        if (protectionCircuit) {
            synths[sourceCurrencyKey].burn(messageSender, sourceAmount);
            return true;
        } else {
            // Pass it along, defaulting to the sender as the recipient.
            return _internalExchange(
                messageSender,
                sourceCurrencyKey,
                sourceAmount,
                destinationCurrencyKey,
                messageSender,
                true // Charge fee on the exchange
            );
        }
    }


/**
 * @notice Function that allows synth contract to delegate exchanging of a synth that is not the same sourceCurrency
 * @dev Only the synth contract can call this function
 * @param from The address to exchange / burn synth from
 * @param sourceCurrencyKey The source currency you wish to exchange from
 * @param sourceAmount The amount, specified in UNIT of source currency you wish to exchange
 * @param destinationCurrencyKey The destination currency you wish to obtain.
 * @param destinationAddress Where the result should go.
 * @return Boolean that indicates whether the transfer succeeded or failed.
 */

function synthInitiatedExchange(
        address from,
        bytes32 sourceCurrencyKey,
        uint sourceAmount,
        bytes32 destinationCurrencyKey,
        address destinationAddress
    )
        external
        optionalProxy
        returns (bool)
    {
        require(synthsByAddress[messageSender] != bytes32(0), "Only synth allowed");
        require(sourceCurrencyKey != destinationCurrencyKey, "Can't be same synth");
        require(sourceAmount > 0, "Zero amount");

        // Pass it along
        return _internalExchange(
            from,
            sourceCurrencyKey,
            sourceAmount,
            destinationCurrencyKey,
            destinationAddress,
            false
        );
    }


/**
 * @notice Issue synths against the sender's SNX.
 * @dev Issuance is only allowed if the synthetix price isn't stale. Amount should be larger than 0.
 * @param amount The amount of synths you wish to issue with a base of UNIT
 */

function issueSynths(uint amount)
        public
        optionalProxy
        // No need to check if price is stale, as it is checked in issuableSynths.
    {
        bytes32 currencyKey = "sUSD";

        require(amount <= remainingIssuableSynths(messageSender, currencyKey), "Amount too large");

        // Keep track of the debt they're about to create
        _addToDebtRegister(currencyKey, amount);

        // Create their synths
        synths[currencyKey].issue(messageSender, amount);

        // Store their locked SNX amount to determine their fee % for the period
        _appendAccountIssuanceRecord();
    }


/**
 * @notice Issue the maximum amount of Synths possible against the sender's SNX.
 * @dev Issuance is only allowed if the synthetix price isn't stale.
 */

function issueMaxSynths()
        external
        optionalProxy
    {
        bytes32 currencyKey = "sUSD";

        // Figure out the maximum we can issue in that currency
        uint maxIssuable = remainingIssuableSynths(messageSender, currencyKey);

        // Keep track of the debt they're about to create
        _addToDebtRegister(currencyKey, maxIssuable);

        // Create their synths
        synths[currencyKey].issue(messageSender, maxIssuable);

        // Store their locked SNX amount to determine their fee % for the period
        _appendAccountIssuanceRecord();
    }