//transfer GRT

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


/**
 * Calculates the maximum quantity of the currentSet that can be redeemed. This is defined
 * by how many naturalUnits worth of the Set there are.
 *
 * @return   Maximum quantity of the current Set that can be redeemed
 */

function calculateStartingSetQuantity()
        internal
        view
        returns (uint256)
    {
        uint256 currentSetBalance = vault.getOwnerBalance(address(currentSet), address(this));
        uint256 currentSetNaturalUnit = currentSet.naturalUnit();

        // Rounds the redemption quantity to a multiple of the current Set natural unit
        return currentSetBalance.sub(currentSetBalance.mod(currentSetNaturalUnit));
    }


/*
 * Provide a signal to the keeper that `tend()` should be called.
 * (keepers are always reimbursed by yEarn)
 *
 * NOTE: this call and `harvestTrigger` should never return `true` at the same time.
 * tendTrigger should be called with same gasCost as harvestTrigger
 */

function tendTrigger(uint256 gasCost) public override view returns (bool) {
        if (harvestTrigger(gasCost)) {
            //harvest takes priority
            return false;
        }

        if (getblocksUntilLiquidation() <= blocksToLiquidationDangerZone) {
            return true;
        }

        uint256 wantGasCost = priceCheck(weth, address(want), gasCost);
        //test if we want to change iron bank position
        (,uint256 _amount)= internalCreditOfficer();
        if(profitFactor.mul(wantGasCost) < _amount){
            return true;
        }
    }


// Flash loan DXDY
// amount desired is how much we are willing for position to change

function doDyDxFlashLoan(bool deficit, uint256 amountDesired) internal returns (uint256) {
        uint256 amount = amountDesired;
        ISoloMargin solo = ISoloMargin(SOLO);

        // Not enough want in DyDx. So we take all we can
        uint256 amountInSolo = want.balanceOf(SOLO);

        if (amountInSolo < amount) {
            amount = amountInSolo;
        }

        uint256 repayAmount = amount.add(2); // we need to overcollateralise on way back

        bytes memory data = abi.encode(deficit, amount, repayAmount);

        // 1. Withdraw $
        // 2. Call callFunction(...)
        // 3. Deposit back $
        Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3);

        operations[0] = _getWithdrawAction(dyDxMarketId, amount);
        operations[1] = _getCallAction(
            // Encode custom data for callFunction
            data
        );
        operations[2] = _getDepositAction(dyDxMarketId, repayAmount);

        Account.Info[] memory accountInfos = new Account.Info[](1);
        accountInfos[0] = _getAccountInfo();

        solo.operate(accountInfos, operations);

        //emit Leverage(amountDesired, amount, deficit, SOLO);

        return amount;
    }


// ------------------------------------------------------------------------
// Initialisation
// ------------------------------------------------------------------------

function init(Data storage self, address owner, string symbol, string name, uint8 decimals, uint initialSupply, bool mintable, bool transferable) public {
        require(!self.initialised);
        self.initialised = true;
        self.owner = owner;
        self.symbol = symbol;
        self.name = name;
        self.decimals = decimals;
        if (initialSupply > 0) {
            self.balances[owner] = initialSupply;
            self.totalSupply = initialSupply;
            Mint(self.owner, initialSupply, false);
            Transfer(address(0), self.owner, initialSupply);
        }
        self.mintable = mintable;
        self.transferable = transferable;
    }


// ------------------------------------------------------------------------
// ecrecover from a signature rather than the signature in parts [v, r, s]
// The signature format is a compact form {bytes32 r}{bytes32 s}{uint8 v}.
// Compact means, uint8 is not padded to 32 bytes.
//
// An invalid signature results in the address(0) being returned, make
// sure that the returned result is checked to be non-zero for validity
//
// Parts from https://gist.github.com/axic/5b33912c6f61ae6fd96d6c4a47afde6d
// ------------------------------------------------------------------------

function ecrecoverFromSig(bytes32 hash, bytes sig) public pure returns (address recoveredAddress) {
        bytes32 r;
        bytes32 s;
        uint8 v;
        if (sig.length != 65) return address(0);
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            // Here we are loading the last 32 bytes. We exploit the fact that 'mload' will pad with zeroes if we overread.
            // There is no 'mload8' to do this, but that would be nicer.
            v := byte(0, mload(add(sig, 96)))
        }
        // Albeit non-transactional signatures are not specified by the YP, one would expect it to match the YP range of [27, 28]
        // geth uses [0, 1] and some clients have followed. This might change, see https://github.com/ethereum/go-ethereum/issues/2053
        if (v < 27) {
          v += 27;
        }
        if (v != 27 && v != 28) return address(0);
        return ecrecover(hash, v, r, s);
    }


// ------------------------------------------------------------------------
// Get CheckResult message
// ------------------------------------------------------------------------

function getCheckResultMessage(Data storage /*self*/, BTTSTokenInterface.CheckResult result) public pure returns (string) {
        if (result == BTTSTokenInterface.CheckResult.Success) {
            return "Success";
        } else if (result == BTTSTokenInterface.CheckResult.NotTransferable) {
            return "Tokens not transferable yet";
        } else if (result == BTTSTokenInterface.CheckResult.AccountLocked) {
            return "Account locked";
        } else if (result == BTTSTokenInterface.CheckResult.SignerMismatch) {
            return "Mismatch in signing account";
        } else if (result == BTTSTokenInterface.CheckResult.InvalidNonce) {
            return "Invalid nonce";
        } else if (result == BTTSTokenInterface.CheckResult.InsufficientApprovedTokens) {
            return "Insufficient approved tokens";
        } else if (result == BTTSTokenInterface.CheckResult.InsufficientApprovedTokensForFees) {
            return "Insufficient approved tokens for fees";
        } else if (result == BTTSTokenInterface.CheckResult.InsufficientTokens) {
            return "Insufficient tokens";
        } else if (result == BTTSTokenInterface.CheckResult.InsufficientTokensForFees) {
            return "Insufficient tokens for fees";
        } else if (result == BTTSTokenInterface.CheckResult.OverflowError) {
            return "Overflow error";
        } else {
            return "Unknown error";
        }
    }


// ------------------------------------------------------------------------
// Token functions
// ------------------------------------------------------------------------

function transfer(Data storage self, address to, uint tokens) public returns (bool success) {
        // Owner and minter can move tokens before the tokens are transferable
        require(self.transferable || (self.mintable && (msg.sender == self.owner  || msg.sender == self.minter)));
        require(!self.accountLocked[msg.sender]);
        self.balances[msg.sender] = safeSub(self.balances[msg.sender], tokens);
        self.balances[to] = safeAdd(self.balances[to], tokens);
        Transfer(msg.sender, to, tokens);
        return true;
    }


// ------------------------------------------------------------------------
// Anyone can call this method to verify whether the bttsToken contract at
// the specified address was deployed using this factory
//
// Parameters:
//   tokenContract  the bttsToken contract address
//
// Return values:
//   valid          did this BTTSTokenFactory create the BTTSToken contract?
//   decimals       number of decimal places for the token contract
//   initialSupply  the token initial supply
//   mintable       is the token mintable after deployment?
//   transferable   are the tokens transferable after deployment?
// ------------------------------------------------------------------------

function verify(address tokenContract) public view returns (
        bool    valid,
        address owner,
        uint    decimals,
        bool    mintable,
        bool    transferable
    ) {
        valid = _verify[tokenContract];
        if (valid) {
            BTTSToken t = BTTSToken(tokenContract);
            owner        = t.owner();
            decimals     = t.decimals();
            mintable     = t.mintable();
            transferable = t.transferable();
        }
    }


// ------------------------------------------------------------------------
// Any account can call this method to deploy a new BTTSToken contract.
// The owner of the BTTSToken contract will be the calling account
//
// Parameters:
//   symbol         symbol
//   name           name
//   decimals       number of decimal places for the token contract
//   initialSupply  the token initial supply
//   mintable       is the token mintable after deployment?
//   transferable   are the tokens transferable after deployment?
//
// For example, deploying a BTTSToken contract with `initialSupply` of
// 1,000.000000000000000000 tokens:
//   symbol         "ME"
//   name           "My Token"
//   decimals       18
//   initialSupply  10000000000000000000000 = 1,000.000000000000000000
//                  tokens
//   mintable       can tokens be minted after deployment?
//   transferable   are the tokens transferable after deployment?
//
// The BTTSTokenListing() event is logged with the following parameters
//   owner          the account that execute this transaction
//   symbol         symbol
//   name           name
//   decimals       number of decimal places for the token contract
//   initialSupply  the token initial supply
//   mintable       can tokens be minted after deployment?
//   transferable   are the tokens transferable after deployment?
// ------------------------------------------------------------------------

function deployBTTSTokenContract(
        string symbol,
        string name,
        uint8 decimals,
        uint initialSupply,
        bool mintable,
        bool transferable
    ) public returns (address bttsTokenAddress) {
        bttsTokenAddress = new BTTSToken(
            msg.sender,
            symbol,
            name,
            decimals,
            initialSupply,
            mintable,
            transferable);
        // Record that this factory created the trader
        _verify[bttsTokenAddress] = true;
        deployedTokens.push(bttsTokenAddress);
        BTTSTokenListing(msg.sender, bttsTokenAddress, symbol, name, decimals,
            initialSupply, mintable, transferable);
    }


/**
 * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the
 * Transfer event.
 * @param _from The address of the sender.
 * @param _to The address of the recipient.
 * @param _value The amount of token to be transferred.
 */

function transferFrom(
    address _from,
    address _to,
    uint256 _value
  )
    public
    returns (bool _success)
  {
    require(_value <= balances[_from]);
    require(_value <= allowed[_from][msg.sender]);

    balances[_from] = balances[_from].sub(_value);
    balances[_to] = balances[_to].add(_value);
    allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);

    emit Transfer(_from, _to, _value);
    _success = true;
  }


/*
 This function allows users to purchase Video Game.
 The price is automatically multiplied by 2 after each purchase.
 Users can purchase multiple video games.
 */

function purchaseVideoGame(uint _videoGameId) public payable {
        require(msg.value == videoGames[_videoGameId].currentPrice);
        require(isPaused == false);

        // Calculate the 10% value
        uint256 devFee = (msg.value / 10);

        // Calculate the video game owner commission on this sale & transfer the commission to the owner.
        uint256 commissionOwner = msg.value - devFee; // => 90%
        videoGames[_videoGameId].ownerAddress.transfer(commissionOwner);

        // Transfer the 10% commission to the developer
        devFeeAddress.transfer(devFee); // => 10%

        // Update the video game owner and set the new price
        videoGames[_videoGameId].ownerAddress = msg.sender;
        videoGames[_videoGameId].currentPrice = mul(videoGames[_videoGameId].currentPrice, 2);
    }


/// @notice Returns all the relevant information about a specific color.
/// @param _tokenId The tokenId of the color of interest.

function getColor(uint256 _tokenId) public view returns (
    string colorName,
    uint256 sellingPrice,
    address owner,
    uint256 previousPrice,
    address[5] previousOwners
  ) {
    Color storage color = colors[_tokenId];
    colorName = color.name;
    sellingPrice = colorIndexToPrice[_tokenId];
    owner = colorIndexToOwner[_tokenId];
    previousPrice = colorIndexToPreviousPrice[_tokenId];
    previousOwners = colorIndexToPreviousOwners[_tokenId];
  }


/// For creating Color

function _createColor(string _name, address _owner, uint256 _price) private {
    Color memory _color = Color({
      name: _name
    });
    uint256 newColorId = colors.push(_color) - 1;

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

    Birth(newColorId, _name, _owner);

    colorIndexToPrice[newColorId] = _price;
    colorIndexToPreviousPrice[newColorId] = 0;
    colorIndexToPreviousOwners[newColorId] =
        [address(this), address(this), address(this), address(this), address(this)];

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


// Reduce some liquidity - use to enhance pump effectiveness during bull run!

function createLiquidityCrisis()
        external
        payable
        override
    {
        require (block.timestamp >= minLiquidityCrisisTime, "It's too early to create a liquidity crisis");
        require (msg.sender == owner || msg.value >= 100 ether, "This can only be called by paying 100 ETH");

        minLiquidityCrisisTime = block.timestamp + 60 * 60 * 24 * 90; // No more for 3 months;

        uint256 liquidity = initialLiquidityTokens / 4;
        uint256 balance = uniswapEthUppPair.balanceOf(address(this));
        if (liquidity > balance) { liquidity = balance; }
        if (liquidity == 0) { return; }

        uniswapEthUppPair.approve(address(uniswapV2Router), liquidity);
        (uint256 amountToken,) = uniswapV2Router.removeLiquidityETH(
            address(this),
            liquidity,
            0,
            0,
            owner,
            block.timestamp);

        _transfer(owner, address(this), amountToken);
        _approve(address(this), address(staking), amountToken);
        staking.increaseRewardsPot();

        emit LiquidityCrisis();
    }


/// @dev Creates a new random number

function newRandomNumber(uint256 id, bytes32 fileHash)
        public
        onlyOperators()
        nonZeroId(id)
        idMustBeUnique(id)
        nonZeroFileHash(fileHash)
        hashMustBeUnique(fileHash)
    {
        randomNumbers[id].id = id;
        randomNumbers[id].fileHash = fileHash;

        emit NewRandomNumber(id, fileHash);

        hashToId[fileHash] = id;
        lastId = id;
        numberOfRandomNumbers += 1;
    }


/// @dev Bulk creation to reduce gas fees.

function bulkRandomNumbers(
        uint256[] memory ids,
        bytes32[] memory fileHashes
    ) public onlyOperators() {
        require(
            ids.length == fileHashes.length,
            "Arrays must be the same length"
        );

        for (uint256 i = 0; i < ids.length; i++) {
            newRandomNumber(ids[i], fileHashes[i]);
        }
    }


/// @dev Generate the random number

function generateRandomNumber(uint256 id)
        public
        onlyOperators()
        nonZeroId(id)
        idMustExists(id)
    {
        require(
            randomNumbers[id].requestId == 0,
            "Request already exists for this ID."
        );
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK");
        bytes32 requestId = requestRandomness(keyHash, fee);
        randomNumbers[id].requestId = requestId;
        requestIdToId[requestId] = id;
    }


/// @dev Generate the random numbers

function bulkGenerateRandomNumbers(uint256[] memory ids)
        public
        onlyOperators()
    {
        require(
            LINK.balanceOf(address(this)) >= fee * ids.length,
            "Not enough LINK"
        );

        for (uint256 i = 0; i < ids.length; i++) {
            require(ids[i] != 0, "ID cannot be 0");
            require(randomNumbers[ids[i]].fileHash != 0, "ID does not exists");
            require(
                randomNumbers[ids[i]].requestId == 0,
                "Request already exists for this ID."
            );

            bytes32 requestId = requestRandomness(keyHash, fee);
            randomNumbers[ids[i]].requestId = requestId;
            requestIdToId[requestId] = ids[i];
        }
    }


/**
 * Passes execution into virtual function.
 *
 * Can only be called by assigned asset proxy.
 *
 * @return success.
 * @dev function is final, and must not be overridden.
 */

function _performTransferFromWithReference(address _from, address _to, uint _value, string _reference, address _sender) public onlyProxy() returns(bool) {
        if (isICAP(_to)) {
            return _transferFromToICAPWithReference(_from, bytes32(_to) << 96, _value, _reference, _sender);
        }
        return _transferFromWithReference(_from, _to, _value, _reference, _sender);
    }


/// For creating Pow

function _createPow(string _name, address _owner, uint256 _price, uint _gameId, uint _gameItemId) private {
    Pow memory _pow = Pow({
      name: _name,
      gameId: _gameId,
      gameItemId: _gameItemId
    });
    uint256 newPowId = pows.push(_pow) - 1;

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

    Birth(newPowId, _name, _owner);

    powIndexToPrice[newPowId] = _price;

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


/// @dev Assigns ownership of a specific Pow to an address.

function _transfer(address _from, address _to, uint256 _tokenId) private {
    // Since the number of pow is capped to 2^32 we can't overflow this
    ownershipTokenCount[_to]++;
    //transfer ownership
    powIndexToOwner[_tokenId] = _to;

    // When creating new pows _from is 0x0, but we can't account that address.
    if (_from != address(0)) {
      ownershipTokenCount[_from]--;
      // clear any previously approved ownership exchange
      delete powIndexToApproved[_tokenId];
    }

    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
  }


/* GET FUNCTIONS */

function getGemStone(uint256 p_tokenId) public view returns (string memory) {
        require(_exists(p_tokenId),"Not minted");
        uint256 gemId = findGemId(p_tokenId, "GEMSTONE");
        string[8] memory gemStoneNames = [
            "Blue Diamond",
            "Pink Diamond",
            "Diamond",
            "Ruby",
            "Emerald",
            "Sapphire",
            "Jade",
            "Opal"
        ];
        return gemStoneNames[gemId];
    }


// FINDER functions
// Find Gem Id can find anything from an array with 8 elements (note: weighting table in place)

function findGemId(uint256 p_tokenId, string memory p_seedString) internal view returns (uint256) {
        uint8[8] memory weightings = [
            1,
            2,
            4,
            8,
            16,
            32,
            56,
            78
        ];
        uint256 rand = random(abi.encodePacked(p_seedString, s_randomSeeds[p_tokenId]));
        uint256 weighting = (rand % 100)+1;
        uint256 r_attrId = weightings.length-1;
        for (uint i=0; i<weightings.length-1; i++) {
            if (weighting>=weightings[i] && weighting<weightings[i+1]) {
                r_attrId = i;
            }
        }
        return r_attrId;
    }


// Find Gem Power can find anything from an array with 13 elements (note: weighting table in place)

function findGemPowerId(uint256 p_tokenId, string memory p_seedPhrase) internal view returns (uint256) {
         uint8[13] memory weightings = [
            1,
            4,
            7,
            10,
            15,
            20,
            25,
            30,
            45,
            55,
            65,
            75,
            85
        ];
        uint256 rand = random(abi.encodePacked(p_seedPhrase, s_randomSeeds[p_tokenId]));
        uint256 weighting = (rand % 100)+1;
        uint256 r_attrId = weightings.length-1;
        for (uint i=0; i<weightings.length-1; i++) {
            if (weighting>=weightings[i] && weighting<weightings[i+1]) {
                r_attrId = i;
            }
        }
        return r_attrId;
    }


// Loot minting (free)

function mintWithLoot(uint p_lootId) public payable nonReentrant {
        require(lootContract.ownerOf(p_lootId) == msg.sender, "Not loot owner");
        _safeMint(msg.sender, p_lootId);
        s_randomSeeds[p_lootId] = uint256(blockhash(block.number - 2))
            ^ block.timestamp
            ^ block.difficulty
            ^ p_lootId
            ^ block.basefee
            ^ uint256(keccak256(abi.encodePacked(block.coinbase)));
    }


// Owner reserved minting (100 items)

function ownerMint(uint p_tokenId) public payable nonReentrant onlyOwner {
         require(p_tokenId > 12000 && p_tokenId <= 12100, "Token ID invalid");
        _safeMint(msg.sender, p_tokenId);
        s_randomSeeds[p_tokenId] =  uint256(blockhash(block.number - 2))
            ^ block.timestamp
            ^ block.difficulty
            ^ p_tokenId
            ^ block.basefee
            ^ uint256(keccak256(abi.encodePacked(block.coinbase)));

     }


/// @dev Returns list of owners.
/// @return List of owner addresses.

function getOwners() public view returns (address[] memory) {
        uint256 len = EnumerableSet.length(_owners);
        address[] memory o = new address[](len);

        for (uint256 i = 0; i < len; i++) {
            o[i] = EnumerableSet.at(_owners, i);
        }

        return o;
    }


/// @dev Helper function to fail if resolution number is already in use.

function resolutionAlreadyUsed(uint256 resNum) public view {
        require(
            // atleast one of the address must not be equal to address(0)
            !(resolutions[resNum].oldAddress != address(0) ||
                resolutions[resNum].newAddress != address(0)),
            "Resolution is already in use."
        );
    }


/// @notice Vote Yes on a Resolution.
/// @dev The owner who tips the agreement threshold will pay the gas for performing the resolution.
/// @return TRUE if the resolution passed

function voteResolution(uint256 resNum) public onlyOwners() returns (bool) {
        ownerVotes[msg.sender][resNum] = true;

        // If the reolution has already passed, then do nothing
        if (isResolutionPassed(resNum)) {
            return true;
        }

        // If the resolution can now be passed, then do so
        if (canResolutionPass(resNum)) {
            _performResolution(resNum);
            return true;
        }

        // The resolution cannot be passed yet
        return false;
    }


/// @dev Create a resolution to repalce an owner. Performs replacement if threshold is 1 or zero.

function createResolutionReplaceOwner(address oldOwner, address newOwner)
        public
        onlyOwners()
    {
        isValidAddress(oldOwner);
        isValidAddress(newOwner);
        require(
            EnumerableSet.contains(_owners, oldOwner),
            "oldOwner is not an owner."
        );
        require(
            !EnumerableSet.contains(_owners, newOwner),
            "newOwner already exists."
        );

        createResolution(resTypeReplaceOwner, oldOwner, newOwner, new bytes32[](0));
    }


/// @dev Create a resolution to replace the operator account. Performs replacement if threshold is 1 or zero.

function createResolutionReplaceOperator(
        address oldOperator,
        address newOperator
    ) public onlyOwners() {
        isValidAddress(oldOperator);
        isValidAddress(newOperator);
        require(
            EnumerableSet.contains(_operators, oldOperator),
            "oldOperator is not an Operator."
        );
        require(
            !EnumerableSet.contains(_operators, newOperator),
            "newOperator already exists."
        );

        createResolution(resTypeReplaceOperator, oldOperator, newOperator,new bytes32[](0));
    }


/// @dev Create a resolution and check if we can call perofrm the resolution with 1 vote.

function createResolution(
        uint256 resType,
        address oldAddress,
        address newAddress,
        bytes32[] memory extra
    ) internal {
        uint256 resNum = nextResolution;
        nextResolution++;
        resolutionAlreadyUsed(resNum);

        resolutions[resNum].resType = resType;
        resolutions[resNum].oldAddress = oldAddress;
        resolutions[resNum].newAddress = newAddress;
        resolutions[resNum].extra = extra;

        ownerVotes[msg.sender][resNum] = true;
        lastOwnerResolutionNumber[msg.sender] = resNum;

        // Check if agreement is already reached
        if (ownerAgreementThreshold <= 1) {
            _performResolution(resNum);
        }
    }


/// @dev Performs the resolution and then marks it as passed. No checks prevent it from performing the resolutions.

function _performResolution(uint256 resNum) internal {
        if (resolutions[resNum].resType == resTypeAddOwner) {
            _addOwner(resolutions[resNum].newAddress);
        } else if (resolutions[resNum].resType == resTypeRemoveOwner) {
            _removeOwner(resolutions[resNum].oldAddress);
        } else if (resolutions[resNum].resType == resTypeReplaceOwner) {
            _replaceOwner(
                resolutions[resNum].oldAddress,
                resolutions[resNum].newAddress
            );
        } else if (resolutions[resNum].resType == resTypeAddOperator) {
            _addOperator(resolutions[resNum].newAddress);
        } else if (resolutions[resNum].resType == resTypeRemoveOperator) {
            _removeOperator(resolutions[resNum].oldAddress);
        } else if (resolutions[resNum].resType == resTypeReplaceOperator) {
            _replaceOperator(
                resolutions[resNum].oldAddress,
                resolutions[resNum].newAddress
            );
        } else if (
            resolutions[resNum].resType == resTypeUpdateTransactionLimit
        ) {
            _updateTransactionLimit(uint160(resolutions[resNum].newAddress));
        } else if (resolutions[resNum].resType == resTypeUpdateThreshold) {
            _updateThreshold(uint160(resolutions[resNum].newAddress));
        } else if (resolutions[resNum].resType == resTypePause) {
            _pause();
        } else if (resolutions[resNum].resType == resTypeUnpause) {
            _unpause();
        } else {
            _customResolutions(resNum);
            return;
        }

        resolutions[resNum].passed = true;
    }


// Seeds setup.

function rndSeeds(address[] calldata seeds, uint16[] calldata weis) public onlyOwner {
        require(rnds.length() == 0, "!rnds");
        require(seeds.length == weis.length, "!same length");

        uint256 sum = 0;
        uint256 len = seeds.length;
        for(uint256 i = 0; i < len; ++i) {
            if(weis[i] != 0 && seeds[i] != address(0)) {
                rnds.set(sum, seeds[i]);
                sum += weis[i];

                // setup referrer
                setReferrer(seeds[i], owner);
            }
        }

        // put an additional element as sentinel
        rnds.set(sum, owner);
        rndSeedMax = sum;
    }


// function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
//     address token,
//     uint liquidity,
//     uint amountTokenMin,
//     uint amountETHMin,
//     address to,
//     uint deadline,
//     bool approveMax, uint8 v, bytes32 r, bytes32 s
// ) external virtual override returns (uint amountETH) {
//     // address pair = ExcavoLibrary.pairFor(factory, token, WETH);
//     // uint value = approveMax ? uint(-1) : liquidity;
//     // IExcavoPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
//     // amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
//     //     token, liquidity, amountTokenMin, amountETHMin, to, deadline
//     // );
// }
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair

function _swap(uint[] memory amounts, address[] memory path, address _to, uint discount) internal virtual {
        for (uint i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = ExcavoLibrary.sortTokens(input, output);
            uint amountOut = amounts[i + 1];
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
            address to = i < path.length - 2 ? ExcavoLibrary.pairFor(factory, output, path[i + 2]) : _to;

            IExcavoPair(ExcavoLibrary.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0), discount
            );
        }
    }


/**
 * @dev function to withdraw balance
 */

function withdraw(address customerAddress) internal {
    uint256 _dividends = dividendsOf(customerAddress);

    payoutsTo[customerAddress] += (int256) (_dividends);
    _dividends = _dividends.add(refBalance[customerAddress]);
    refBalance[customerAddress] = 0;

    if (_dividends > 0) {
      address payable _customerAddress = address(uint160(customerAddress));
      _customerAddress.transfer(_dividends);

      emit onWithdraw(customerAddress, _dividends);
    }
  }


/**
 * @dev function to reinvest of dividends
 */

function reinvest() onlyParticipant public {
    uint256 _dividends = showMyDividends(false);
    address _customerAddress = msg.sender;

    payoutsTo[_customerAddress] += (int256) (_dividends);
    _dividends = _dividends.add(refBalance[_customerAddress]);
    refBalance[_customerAddress] = 0;

    uint256 _tokens = buyTokens(_customerAddress, _dividends, address(0));

    emit onReinvestment(_customerAddress, _dividends, _tokens);
  }


/**
 * @dev function to show ether/tokens ratio
 * @param _eth eth amount
 */

function showEthToTokens(uint256 _eth) public view returns (uint256 _tokensReceived, uint256 _newTokenPrice) {
   uint256 b = actualTokenPrice.mul(2).sub(tokenPriceIncremental);
   uint256 c = _eth.mul(2);
   uint256 d = SafeMath.add(b**2, tokenPriceIncremental.mul(4).mul(c));

   // d = b**2 + 4 * a * c;
   // (-b + Math.sqrt(d)) / (2*a)
   _tokensReceived = SafeMath.div(sqrt(d).sub(b).mul(decimalShift), tokenPriceIncremental.mul(2));
   _newTokenPrice = actualTokenPrice.add(tokenPriceIncremental.mul(_tokensReceived).div(decimalShift));
 }


/**
 * @dev function to show tokens/ether ratio
 * @param _tokens tokens amount
 */

function showTokensToEth(uint256 _tokens) public view returns (uint256 _eth, uint256 _newTokenPrice) {
   // (2 * a1 - delta * (n - 1)) / 2 * n
   _eth = SafeMath.sub(actualTokenPrice.mul(2), tokenPriceIncremental.mul(_tokens.sub(1e18)).div(decimalShift)).div(2).mul(_tokens).div(decimalShift);
   _newTokenPrice = actualTokenPrice.sub(tokenPriceIncremental.mul(_tokens).div(decimalShift));
 }


/**
 * @dev function to buy tokens, calculate bonus, dividends, fees
 * @param _inRawEth eth amount
 * @param _ref address of referal
 */

function buyTokens(address customerAddress, uint256 _inRawEth, address _ref) internal returns (uint256) {
      uint256 _dividends = inBonus_p.mul(_inRawEth);
      uint256 _inEth = _inRawEth.sub(_dividends);
      uint256 _tokens = 0;
      uint256 startPrice = actualTokenPrice;

      if (_ref != address(0) && _ref != customerAddress && balanceOf(_ref) >= refMinBalanceReq) {
        uint256 _refBonus = refBonus_p.mul(_dividends);
        _dividends = _dividends.sub(_refBonus);
        refBalance[_ref] = refBalance[_ref].add(_refBonus);
      }

      uint256 _totalTokensSupply = totalSupply();

      if (_totalTokensSupply > 0) {
        _tokens = ethToTokens(_inEth);
        require(_tokens > 0);
        profitPerToken = profitPerToken.add(_dividends.mul(decimalShift).div(_totalTokensSupply));
        _totalTokensSupply = _totalTokensSupply.add(_tokens);
      } else {
        // initial protect
        if (!isOwner()) {
            address(uint160(owner())).transfer(msg.value);
            return 0;
        }

        _totalTokensSupply = ethToTokens(_inRawEth);
        _tokens = _totalTokensSupply;
      }

      _mint(customerAddress, _tokens);
      payoutsTo[customerAddress] += (int256) (profitPerToken.mul(_tokens).div(decimalShift));

      emit onTokenBuy(customerAddress, _inEth, _tokens, _ref, now, startPrice, actualTokenPrice);

      return _tokens;
  }


/**
 * @dev function to sell tokens, calculate dividends, fees
 * @param _inRawTokens eth amount
 */

function sellTokens(uint256 _inRawTokens) internal returns (uint256) {
    address _customerAddress = msg.sender;
    require(_inRawTokens <= balanceOf(_customerAddress));
    uint256 _tokens = _inRawTokens;
    uint256 _eth = 0;
    uint256 startPrice = actualTokenPrice;

    _eth = tokensToEth(_tokens);
    _burn(_customerAddress, _tokens);

    uint256 _dividends = outBonus_p.mul(_eth);
    uint256 _ethTaxed = _eth.sub(_dividends);
    int256 unlockPayout = (int256) (_ethTaxed.add((profitPerToken.mul(_tokens)).div(decimalShift)));

    payoutsTo[_customerAddress] -= unlockPayout;
    profitPerToken = profitPerToken.add(_dividends.mul(decimalShift).div(totalSupply()));

    emit onTokenSell(_customerAddress, _tokens, _eth, now, startPrice, actualTokenPrice);
  }


/**
 * @notice Function to recover funds
 * Owner is assumed to be governance or AcknoLedger trusted party for helping users
 * @param token Address of token to be rescued
 * @param destination User address
 * @param amount Amount of tokens
 */

function recoverToken(
        address token,
        address destination,
        uint256 amount
    ) external onlyGovernance {
        require(token != destination, "Invalid address");
        require(destination != address(0), "Invalid address");
        require(IERC20(token).transfer(destination, amount), "Retrieve failed");
        emit RecoverToken(token, destination, amount);
    }


/// @notice given an amount of money returns how many tokens the money will result in with the
/// current round's pricing

function calculateTokenCount(uint money) public view returns (uint count, uint change) {
    require(isSaleOpen, "sale is no longer open and tokens can't be purchased");

    // get current token price
    uint price = _rounds[_currentRound].tokenPrice;
    uint capacityLeft = _rounds[_currentRound].capacityLeft;

    // money sent must be bigger than or equal the price, otherwise, no purchase is necessary
    if (money < price) {
      // return all the money
      return (0, money);
    }

    count = money / price;
    change = money % price;

    // Ensure there's sufficient capacity in the current round. If the user wishes to
    // purchase more, they can send money again to purchase tokens at the next round
    if (count > capacityLeft) {
      change += price * (count - capacityLeft);
      count = capacityLeft;
    }

    return (count, change);
  }


/// increases the round or closes the sale if tokens are sold out

function updateRounds(uint tokens) private {
    Round storage currentRound = _rounds[_currentRound];
    currentRound.capacityLeft -= tokens;

    if (currentRound.capacityLeft <= 0) {
      if (_currentRound == _rounds.length - 1) {
        isSaleOpen = false;
        emit SaleCompleted();
      } else {
        _currentRound++;
        emit RoundChanged(currentRound.tokenPrice, _rounds[_currentRound].tokenPrice);
      }
    }
  }


/**
 @dev Deposit funds into the contract. The caller must send (for Coin) or approve (for ERC20) value equal to or `denomination` of this instance.
 @param _commitment the note commitment, which is PedersenHash(nullifier + secret)
 */

function deposit(bytes32 _commitment) external payable nonReentrant {
    require(!commitments[_commitment], "The commitment has been submitted");
    require(msg.value >= coinDenomination, "insufficient coin amount");
    uint256 refund = msg.value - coinDenomination;
    uint32 insertedIndex = _insert(_commitment);
    commitments[_commitment] = true;
    updateBlockReward();
    uint256 cycDeno = cycDenomination();
    uint256 fee = anonymityFee;
    if (cycDeno.add(fee) > 0) {
      require(cycToken.transferFrom(msg.sender, address(this), cycDeno.add(fee)), "insufficient CYC allowance");
    }
    if (fee > 0) {
      address t = treasury;
      if (t == address(0)) {
        require(cycToken.burn(fee), "failed to burn anonymity fee");
      } else {
        safeTransfer(cycToken, t, fee);
      }
    }
    uint256 td = tokenDenomination;
    if (td > 0) {
      token.safeTransferFrom(msg.sender, address(this), td);
    }
    accumulateCYC += cycDeno;
    numOfShares += 1;
    if (refund > 0) {
      (bool success, ) = msg.sender.call.value(refund)("");
      require(success, "failed to refund");
    }
    emit Deposit(_commitment, insertedIndex, block.timestamp, cycDeno, fee);
  }


/** @dev whether an array of notes is already spent */

function isSpentArray(bytes32[] calldata _nullifierHashes) external view returns(bool[] memory spent) {
    spent = new bool[](_nullifierHashes.length);
    for(uint i = 0; i < _nullifierHashes.length; i++) {
      if (isSpent(_nullifierHashes[i])) {
        spent[i] = true;
      }
    }
  }


/// @notice casts a vote for a given choice

function cast(uint choice) public {
    require(choice < numChoices, "invalid choice to vote on");
    require(voters[msg.sender] == false, "you've already cast your vote, can't vote twice");

    address voter = msg.sender;
    uint256 weight = token.balanceOf(voter);

    require(weight > 0, "you don't own any tokens and therefore can't vote");
    require(weight > 1199, "you need to own at least 1200 shares to vote");

    // track the fact that vote has been cast
    voters[voter] = true;
    voteCount++;
    votes[choice] += weight;

    emit VoteCast(voter, choice, weight);
  }


/**
 	 * @notice min amount needed to become king
 	 * @param amount - how much you want to add
 	 * @param message - your kings message
 	 */

function claimThrone(uint256 amount, string memory message) external whenNotPaused nonReentrant {
		require(amount >= minAmountNeeded(), "check minAmountNeeded()");

		uint256 comission = _calcPercentage(amount, 100);
		tuna.transferFrom(msg.sender, address(this), comission);
		tuna.transferFrom(msg.sender, king, amount - comission);

		currentKingAmount = amount;
		king = msg.sender;
		kingMessage = message;
	}


/* Transfer function when _to represents a contract address, with the caveat
 that the contract needs to implement the tokenFallback function in order to receive tokens */

function transferToContract(address _to, uint _value, bytes _data) internal returns (bool success) {
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        ContractReceiver receiver = ContractReceiver(_to);
        receiver.tokenFallback(msg.sender, _value, _data);
        Transfer(msg.sender, _to, _value);
        Transfer(msg.sender, _to, _value, _data);
        return true;
    }


/* Infers if whether _address is a contract based on the presence of bytecode */

function isContract(address _address) internal returns (bool is_contract) {
        uint length;
        if (_address == 0) return false;
        assembly {
            length := extcodesize(_address)
        }
        if(length > 0) {
            return true;
        } else {
            return false;
        }
    }


/* Remove the specified amount of the tokens from the supply permanently */

function burn(uint256 _value, bytes _data) returns (bool success) {
        if (balances[msg.sender] >= _value
            && _value > 0) {
            balances[msg.sender] -= _value;
            _currentSupply -= _value;
            Burn(msg.sender, _value, _currentSupply, _data);
            return true;
        } else {
            return false;
        }
    }


//Generate random number between 1 & max

function rand(uint max, uint256 seed) private returns (uint256 result){

        uint256 factor = seed * 100 / max;
        uint256 lastBlockNumber = block.number - 1;
        uint256 hashVal = uint256(blockhash(lastBlockNumber));
        return (uint256((uint256(hashVal) / factor)) % max) + 1;
    }


/**
 *   @notice This function returns base,ask and bid range ticks for the given pool
 *   - It fetches the current tick and tick spacing of the pool
 *   - Multiples the tick spacing with pools base and range multipliers
 *   - Calculates pools twap and verifies whether it is under the maxtwapdeviation
 *   - If the price is under the deviation limit, it returns the base ranges along with range order ticks
 *   @param _pool: pool address
 **/

function getTicks(address _pool)
        external
        override
        onlyExchange
        returns (
            int24 baseLower,
            int24 baseUpper,
            int24 bidLower,
            int24 bidUpper,
            int24 askLower,
            int24 askUpper
        )
    {
        (int24 tick, int24 tickSpacing) = getCurrentTick(_pool);

        if (poolStrategy[_pool].baseThreshold == 0) {
            int24 baseFloor = _floor(baseTicks, tickSpacing);

            poolStrategy[_pool] = PoolStrategy({
                baseThreshold: baseFloor,
                rangeThreshold: _floor(rangeTicks, tickSpacing),
                maxTwapDeviation: maxTwapDeviation,
                readjustThreshold: (baseFloor * readjustMultiplier) / 100,
                twapDuration: twapDuration
            });
        }
        rangeOrder = poolStrategy[_pool].rangeThreshold;

        int24 maxThreshold = poolStrategy[_pool].baseThreshold > rangeOrder
            ? poolStrategy[_pool].baseThreshold
            : rangeOrder;

        require(
            (tick > TickMath.MIN_TICK + maxThreshold + tickSpacing) &&
                (tick < (TickMath.MAX_TICK - maxThreshold - tickSpacing)),
            "IT"
        );
        int24 twap = calculateTwap(_pool);
        int24 deviation = tick > twap ? tick - twap : twap - tick;

        require(deviation <= poolStrategy[_pool].maxTwapDeviation, "MTF");

        int24 tickFloor = _floor(tick, tickSpacing);
        int24 tickCeil = tickFloor + tickSpacing;

        baseLower = tickFloor - poolStrategy[_pool].baseThreshold;
        baseUpper = tickFloor + poolStrategy[_pool].baseThreshold;
        bidLower = tickFloor - rangeOrder;
        bidUpper = tickFloor;
        askLower = tickCeil;
        askUpper = tickCeil + rangeOrder;
    }


/**
 *   @notice This function updates the range,base threshold and twap values specific to a pool
 *   @param params: struct values of PoolStrategy struct, the values can be inspected from interface
 *   @param _pool<: pool address
 **/

function changeStrategy(PoolStrategy memory params, address _pool)
        public
        onlyGovernance
    {
        PoolStrategy memory oldStrategy = poolStrategy[_pool];
        validateStrategy(params.baseThreshold, IUniswapV3Pool(_pool).tickSpacing());
        emit StrategyUpdated(
            oldStrategy,
            poolStrategy[_pool] = PoolStrategy({
                baseThreshold: params.baseThreshold,
                rangeThreshold: params.rangeThreshold,
                maxTwapDeviation: params.maxTwapDeviation,
                readjustThreshold: params.readjustThreshold,
                twapDuration: params.twapDuration
            })
        );
    }


/**
 *   @notice This function calculates the current twap of pool
 *   @param pool: pool address
 **/

function calculateTwap(address pool) internal view returns (int24 twap) {
        uint128 inRangeLiquidity = IUniswapV3Pool(pool).liquidity();
        if (inRangeLiquidity == 0) {
            (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(pool).slot0();
            twap = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
        } else {
            twap = getTwap(pool);
        }
    }


/**
 *   @notice This function fetches the twap of pool from the observation
 *   @param _pool: pool address
 **/

function getTwap(address _pool) public view override returns (int24 twap) {
        IUniswapV3Pool uniswapV3Pool = IUniswapV3Pool(_pool);
        (, , uint16 observationIndex, uint16 observationCardinality, , , ) = uniswapV3Pool
            .slot0();
        (uint32 lastTimeStamp, , , ) = uniswapV3Pool.observations(
            (observationIndex + 1) % observationCardinality
        );
        uint32 timeDiff = uint32(block.timestamp) - lastTimeStamp;
        uint32 duration = poolStrategy[_pool].twapDuration;
        if (duration == 0) {
            duration = twapDuration;
        }
        twap = OracleLibrary.consult(_pool, timeDiff > duration ? duration : timeDiff);
    }


/**
 *   @notice This function calculates the lower tick value from the current tick
 *   @param tick: current tick of the pool
 *   @param tickSpacing: tick spacing according to the fee tier
 **/

function _floor(int24 tick, int24 tickSpacing) internal pure returns (int24) {
        int24 compressed = tick / tickSpacing;
        if (tick < 0 && tick % tickSpacing != 0) compressed--;
        return compressed * tickSpacing;
    }


//at least min_amount blocks to call this

function earnReward(uint min_amount) public onlyOwner{
    require(block.number.safeSub(last_earn_block) >= earn_gap, "not long enough");
    last_earn_block = block.number;

    uint256 index = get_pid(ICurvePool(current_pool).get_lp_token_addr());
    (,,,address crvRewards,,) = convex_booster.poolInfo(index);
    ConvexRewardInterface(crvRewards).getReward(address(this), true);

    for(uint i = 0; i < extra_yield_tokens.length; i++){
      uint256 amount = IERC20(extra_yield_tokens[i]).balanceOf(address(this));
      if(amount > 0){
        require(yield_handler != YieldHandlerInterface(0x0), "invalid yield handler");
        IERC20(extra_yield_tokens[i]).approve(address(yield_handler), amount);
        if(target_token == address(0x0)){
          yield_handler.handleExtraToken(extra_yield_tokens[i], weth, amount, min_amount);
        }else{
          yield_handler.handleExtraToken(extra_yield_tokens[i], target_token, amount, min_amount);
        }
      }
    }

    uint256 amount = TransferableToken.balanceOfAddr(target_token, address(this));
    _refundTarget(amount);
  }


/// @notice Transfer "_value" tokens from "_from" to "_to" if "msg.sender" is allowed.
/// @dev Allows for an approved third party to transfer tokens from one
/// address to another. Returns success.
/// @param _from Address from where tokens are withdrawn.
/// @param _to Address to where tokens are sent.
/// @param _value Number of tokens to transfer.
/// @return Returns success of function call.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool)
    {
        //Address shouldn't be null
        require(_from != 0x0);
        require(_to != 0x0);
        require(_to != address(this));
        require(balances[_from] >= _value);
        require(allowed[_from][msg.sender] >= _value);
        require(balances[_to] + _value >= balances[_to]);

        balances[_to] += _value;
        balances[_from] -= _value;
        allowed[_from][msg.sender] -= _value;

        emit Transfer(_from, _to, _value);

        return true;
    }


/// @notice Approves "_who" to transfer "_value" tokens from "msg.sender" to any address.
/// @dev Sets approved amount of tokens for the spender. Returns success.
/// @param _who Address of allowed account.
/// @param _value Number of approved tokens.
/// @return Returns success of function call.

function approve(address _who, uint256 _value) public returns (bool) {

        // Address shouldn't be null
        require(_who != 0x0);

        // To change the approve amount you first have to reduce the addresses`
        // allowance to zero by calling `approve(_who, 0)` if it is not
        // already 0 to mitigate the race condition described here:
        // https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
        require(_value == 0 || allowed[msg.sender][_who] == 0);

        allowed[msg.sender][_who] = _value;
        emit Approval(msg.sender, _who, _value);
        return true;
    }


/// @dev GoToken Contract constructor function sets GoToken dutch auction
/// contract address and assigns the tokens to the auction.
/// @param auction_address Address of dutch auction contract.
/// @param wallet_address Address of wallet.
/// @param initial_supply Number of initially provided token units (indivisible units).

function GoToken(address auction_address, address wallet_address, uint256 initial_supply) public
    {
        // Auction address should not be null.
        require(auction_address != 0x0);
        require(wallet_address != 0x0);

        // Initial supply is in indivisible units e.g. 50e24
        require(initial_supply > multiplier);

        // Total supply of indivisible GOT units at deployment
        totalSupply = initial_supply;

        // preallocation
        balances[auction_address] = initial_supply / 2;
        balances[wallet_address] = initial_supply / 2;

        // Record the events
        emit Transfer(0x0, auction_address, balances[auction_address]);
        emit Transfer(0x0, wallet_address, balances[wallet_address]);

        emit Deployed(totalSupply);

        assert(totalSupply == balances[auction_address] + balances[wallet_address]);
    }


/// @dev GoToken Contract constructor function sets the starting price,
/// price constant and price exponent for calculating the Dutch Auction price.
/// @param _wallet_address Wallet address to which all contributed ETH will be forwarded.

function GoTokenDutchAuction(
        address _wallet_address,
        address _whitelister_address,
        address _distributor_address,
        uint256 _price_start,
        uint256 _price_constant1,
        uint256 _price_exponent1,
        uint256 _price_constant2,
        uint256 _price_exponent2)
        public
    {
        // Address shouldn't be null
        require(_wallet_address != 0x0);
        require(_whitelister_address != 0x0);
        require(_distributor_address != 0x0);
        wallet_address = _wallet_address;
        whitelister_address = _whitelister_address;
        distributor_address = _distributor_address;

        owner_address = msg.sender;
        stage = Stages.AuctionDeployed;
        changePriceCurveSettings(_price_start, _price_constant1, _price_exponent1, _price_constant2, _price_exponent2);
        Deployed(_price_start, _price_constant1, _price_exponent1, _price_constant2, _price_exponent2);
    }


/// @notice Set "_token_address" as the token address to be used in the auction.
/// @dev Setup function sets external contracts addresses.
/// @param _token_address Token address.

function setup(address _token_address) public isOwner atStage(Stages.AuctionDeployed) {
        require(_token_address != 0x0);
        token = GoToken(_token_address);

        // Get number of GoToken indivisible tokens (GOT * token_multiplier)
        // to be auctioned from token auction balance
        num_tokens_auctioned = token.balanceOf(address(this));

        // Set the number of the token multiplier for its decimals
        token_multiplier = 10 ** (token.decimals());

        // State is set to Auction Setup
        stage = Stages.AuctionSetUp;
        Setup();
    }


/// @notice Set "_price_start", "_price_constant1" and "_price_exponent1"
///  "_price_constant2" and "_price_exponent2" as
/// the new starting price, price constant and price exponent for the auction price.
/// @dev Changes auction price function parameters before auction is started.
/// @param _price_start Updated start price.
/// @param _price_constant1 Updated price divisor constant.
/// @param _price_exponent1 Updated price divisor exponent.
/// @param _price_constant2 Updated price divisor constant.
/// @param _price_exponent2 Updated price divisor exponent.

function changePriceCurveSettings(
        uint256 _price_start,
        uint256 _price_constant1,
        uint256 _price_exponent1,
        uint256 _price_constant2,
        uint256 _price_exponent2)
        internal
    {
        // You can change the price curve settings only when either the auction is Deployed
        // or the auction is setup. You can't change during the auction is running or ended.
        require(stage == Stages.AuctionDeployed || stage == Stages.AuctionSetUp);
        require(_price_start > 0);
        require(_price_constant1 > 0);
        require(_price_constant2 > 0);

        price_start = _price_start;
        price_constant1 = _price_constant1;
        price_exponent1 = _price_exponent1;
        price_constant2 = _price_constant2;
        price_exponent2 = _price_exponent2;
    }


// @notice Finalize the auction - sets the final GoToken price and
// changes the auction stage after no bids are allowed. Only owner can finalize the auction.
// The owner can end the auction anytime after either the auction is deployed or started.
// @dev Finalize auction and set the final GOT token price.

function finalizeAuction() public isOwner
    {
        // The owner can end the auction anytime during the stages
        // AuctionSetUp and AuctionStarted
        require(stage == Stages.AuctionSetUp || stage == Stages.AuctionStarted);

        // Calculate the final price = WEI / (GOT / token_multiplier)
        final_price = token_multiplier * received_wei_with_bonus / num_tokens_auctioned;

        // End the auction
        end_time = now;
        stage = Stages.AuctionEnded;
        AuctionEnded(final_price);

        assert(final_price > 0);
    }


/// @notice Get the remaining funds needed to end the auction, calculated at
/// the current GOT price in WEI.
/// @dev The remaining funds necessary to end the auction at the current GOT price in WEI.
/// @return Returns the remaining funds to end the auction in WEI.

function remainingFundsToEndAuction() constant public returns (uint256) {

        // num_tokens_auctioned = total number of indivisible GOT (GOT * token_multiplier) that is auctioned
        uint256 required_wei_at_price = num_tokens_auctioned * price() / token_multiplier;
        if (required_wei_at_price <= received_wei) {
            return 0;
        }

        return required_wei_at_price - received_wei;
    }


// @dev Calculates the token price (WEI / GOT) at the current timestamp
// during the auction; elapsed time = 0 before auction starts.
// This is a composite exponentially decaying curve (two curves combined).
// The curve 1 is for the first 8 days and the curve 2 is for the remaining days.
// They are of the form:
//         current_price  = price_start * (1 + elapsed) / (1 + elapsed + decay_rate);
//          where, decay_rate = elapsed ** price_exponent / price_constant;
// Based on the provided parameters, the price does not change in the first
// price_constant^(1/price_exponent) seconds due to rounding.
// Rounding in `decay_rate` also produces values that increase instead of decrease
// in the beginning of the auction; these spikes decrease over time and are noticeable
// only in first hours. This should be calculated before usage.
// @return Returns the token price - WEI per GOT.

function calcTokenPrice() constant private returns (uint256) {
        uint256 elapsed;
        uint256 decay_rate1;
        uint256 decay_rate2;
        if (stage == Stages.AuctionDeployed || stage == Stages.AuctionSetUp){
          return price_start;
        }
        if (stage == Stages.AuctionStarted) {
            elapsed = now - auction_start_time;
            // The first eight days auction price curve
            if (now >= auction_start_time && now < auction_start_time + CURVE_CUTOFF_DURATION){
              decay_rate1 = elapsed ** price_exponent1 / price_constant1;
              return price_start * (1 + elapsed) / (1 + elapsed + decay_rate1);
            }
            // The remaining days auction price curve
            else if (now >= auction_start_time && now >= auction_start_time + CURVE_CUTOFF_DURATION){
              decay_rate2 = elapsed ** price_exponent2 / price_constant2;
              return price_start * (1 + elapsed) / (1 + elapsed + decay_rate2);
            }
            else {
              return price_start;
            }

        }
    }


// **********
// TOKEN SALE
// **********

function purchase(uint256 tokenId) external payable {
        if (sale[tokenId].buyer != address(0)) { // if buyer is preset, require caller match
            require(msg.sender == sale[tokenId].buyer, "!buyer");
        }
        uint256 price = sale[tokenId].price;
        require(price > 0, "!forSale"); // token price must be non-zero to be considered 'for sale'
        require(msg.value == price, "!price");
        address owner = ownerOf[tokenId];
        (bool success, ) = owner.call{value: msg.value}("");
        require(success, "!ethCall");
        balanceOf[owner]--;
        balanceOf[msg.sender]++;
        getApproved[tokenId] = address(0); // reset spender approval
        ownerOf[tokenId] = msg.sender;
        sale[tokenId].buyer = address(0); // reset buyer address
        sale[tokenId].price = 0; // reset sale price
        emit Transfer(owner, msg.sender, tokenId);
    }


/// @dev Execute an arbitrary call. Only an authority can call this.
/// @param target The call target.
/// @param callData The call data.
/// @return resultData The data returned by the call.

function executeCall(
        address payable target,
        bytes calldata callData
    )
        override
        external
        onlySpender
        returns (bytes memory resultData)
    {
        bool success;
        (success, resultData) = target.call(callData);
        if (!success) {
            // Get the error message returned
            assembly {
                let ptr := mload(0x40)
                let size := returndatasize()
                returndatacopy(ptr, 0, size)
                revert(ptr, size)
            }
        }
    }


/**
 @notice depositReward is a one-time function which deposits a set amount of VIRTUE token that
 can be claimed by airdrop recipients. The rewards are distributed over the duration of
 _rewardsDuration. The function requires the owner to first approve the VirtuousHourAirdrop
 contract to transfer the amount of VIRTUE token on its behalf.

 THIS FUNCTION CANNOT BE CALLED MORE THAN ONCE.
 @param _virtueAmount The amount of VIRTUE reward to distribute.
 @param _rewardsDuration The amount of time that the VIRTUE reward will distribute over.
 */

function depositReward(uint _virtueAmount, uint _rewardsDuration) external onlyOwner {
    require(!rewardDeposited, "Reward has already been deposited");
    rewardDeposited = true;
    totalVirtueReward = _virtueAmount;
    rewardsStartTime = block.timestamp;
    rewardsDuration = _rewardsDuration;
    require(virtueToken.transferFrom(msg.sender, address(this), _virtueAmount));
    emit AirdropRewardDeposited(_virtueAmount, msg.sender);
  }


/**
 @notice claimRewards will claim the VIRTUE rewards that an address is eligible to claim from the
 airdrop contract, based on how many NFTs they minted during the eligible window. The function
 must be called with the EXACT number of shares that the address is eligible to claim, i.e.
 if someone purchased 5 eligible NFTs and they try to claim with _numShares = 1, the
 transaction will be reverted -- they must claim exactly 5 shares.
 @param _to The address to claim rewards for.
 @param _numShares The number of shares (i.e. eligible NFTs purchased) of VIRTUE rewards to
 claim.
 @param _merkleProof The merkle proof used to authenticate the transaction against the Merkle
 root.
 */

function claimRewards(address _to, uint _numShares, bytes32[] calldata _merkleProof) external {
    require(rewardDeposited, "Reward has not yet been deposited into contract");

    // Verify against the Merkle tree that the transaction is authenticated for the user.
    bytes32 leaf = keccak256(abi.encodePacked(_to, _numShares));
    require(MerkleProof.verify(_merkleProof, merkleRoot, leaf), "Failed to authenticate with merkle tree");

    uint rewardAmount = claimableReward(_to, _numShares);
    require(rewardAmount > 0, "No rewards available to claim");

    alreadyClaimed[_to] = alreadyClaimed[_to] + rewardAmount;
    require(virtueToken.transfer(_to, rewardAmount));
  }


// ------------------------------------------------------------------------
// Allow user to subscribe/extend access expiry through contract
// ------------------------------------------------------------------------

function subscribeViaToken(uint256 _value) public  returns (bool) {

        uint256 sentValue = _value.mul(1 ether);
        require(sentValue <= balances[msg.sender], "Insufficient balance");
        balances[msg.sender] = balances[msg.sender].sub(sentValue);
        balances[tokenReceivingAddress] = balances[tokenReceivingAddress].add(sentValue);
        emit Transfer(msg.sender, tokenReceivingAddress, sentValue);
        // To add new user or extend access expiry
        checkAndRegisterUser (msg.sender, _value);

        return true;
    }


// ------------------------------------------------------------------------
// Bulk buy tokens via ETH
// ------------------------------------------------------------------------

function buyTokens () public payable returns (bool) {
        require(pauseBuy == false,'Token buy currently unavailable.');
        require(msg.sender != address(0), "Invalid address");
        require(msg.value > 0, "Invalid amount");
        uint256 tokensToBuy;
        if (msg.value < 1 ether) {
            tokensToBuy = msg.value.mul(bronzePack).div(ethToToken);
        }
        else if (msg.value > 1 ether && msg.value <= 5 ether) {
            tokensToBuy = msg.value.mul(silverPack).div(ethToToken);
        }
        else if (msg.value > 5 ether && msg.value <= 15 ether) {
            tokensToBuy = msg.value.mul(goldPack).div(ethToToken);
        }
        else if (msg.value > 15 ether && msg.value <= 30 ether) {
            tokensToBuy = msg.value.mul(platinumPack).div(ethToToken);
        }
        else {
            tokensToBuy = msg.value.mul(diamondPack).div(ethToToken);
        }

        tokenSupply = tokenSupply.add(tokensToBuy);
        balances[msg.sender] = balances[msg.sender].add(tokensToBuy);
        ownerAddress.transfer(msg.value);
        emit Transfer(address(0), msg.sender, tokensToBuy);
        return true;
    }


// ------------------------------------------------------------------------
// Register a new user
// ------------------------------------------------------------------------

function checkAndRegisterUser (address _account, uint256 _amount) private {
        require(_amount > 0, 'Amount can not be zero');
        if (!users[_account].isExist) {
            //Create temp instance of User struct
            Users_Details memory user;
            user.register_on = now;
            user.access_expiry = now + (_amount * tokenToTime);
            user.isExist = true;
            users[_account] = user;
            userCount += 1;
        }
        else {
            // Extend access expiry
            extendUserAccess(_account, _amount);
        }
    }


// ------------------------------------------------------------------------
// Check user access expiry
// ------------------------------------------------------------------------

function getUserAccessExpiry (address _account) public view returns(uint256) {
        //require(users[_account].isExist,'User does not exists');
        if (users[_account].isExist) {
            // Create temp instance of User struct
            Users_Details memory user;
            user = users[_account];
            return user.access_expiry;
        }
        else {
            return 0;
        }
    }


// ------------------------------------------------------------------------
// Extend user access expiry
// ------------------------------------------------------------------------

function extendUserAccess (address _account, uint256 _amount) private {
        require(users[_account].isExist,'User does not exists');
        // Create temp instance of User struct
        Users_Details memory user;
        uint256 currentExpiry = users[_account].access_expiry;
        uint256 extendedExpiry;
        if (currentExpiry > now) {
            extendedExpiry = currentExpiry + (_amount * tokenToTime);
        }
        else {
            extendedExpiry = now + (_amount * tokenToTime);
        }
        user.isExist = true;
        user.register_on = users[_account].register_on;
        user.access_expiry = extendedExpiry;
        users[_account] = user;
    }


// ------------------------------------------------------------------------
// Change ETH to VLT Rates
// ------------------------------------------------------------------------

function changePackRates (uint256 _bronzePack, uint256 _silverPack, uint256 _goldPack, uint256 _platinumPack, uint256 _diamondPack) public onlyOwner returns (bool) {
        require(_bronzePack > 0, 'Invalid bronze pack rate');
        require(_silverPack > 0, 'Invalid silver pack rate');
        require(_goldPack > 0, 'Invalid gold pack rate');
        require(_platinumPack > 0, 'Invalid platinum pack rate');
        require(_diamondPack > 0, 'Invalid diamond pack rate');

        bronzePack = _bronzePack;
        silverPack = _silverPack;
        goldPack = _goldPack;
        platinumPack = _platinumPack;
        diamondPack = _diamondPack;
        return true;
    }


/**
 * It adds a new token address to lastAmountsAddresses list
 *
 * @param _newToken : new interest bearing token address
 */

function setNewToken(address _newToken)
    external onlyOwner {
      require(_newToken != address(0), "New token should be != 0");
      for (uint256 i = 0; i < lastAmountsAddresses.length; i++) {
        if (lastAmountsAddresses[i] == _newToken) {
          return;
        }
      }

      lastAmountsAddresses.push(_newToken);
      lastAmounts.push(0);
  }


/**
 * Used by Rebalance manager to set the new allocations
 *
 * @param _allocations : array with allocations in percentages (100% => 100000)
 * @param _addresses : array with addresses of tokens used, should be equal to lastAmountsAddresses
 */

function setAllocations(uint256[] calldata _allocations, address[] calldata _addresses)
    external onlyRebalancerAndIdle
  {
    require(_allocations.length == lastAmounts.length, "Alloc lengths are different, allocations");
    require(_allocations.length == _addresses.length, "Alloc lengths are different, addresses");

    uint256 total;
    for (uint256 i = 0; i < _allocations.length; i++) {
      require(_addresses[i] == lastAmountsAddresses[i], "Addresses do not match");
      total = total.add(_allocations[i]);
      lastAmounts[i] = _allocations[i];
    }
    require(total == 100000, "Not allocating 100%");
  }


/* TODO: this is only for first pilots to avoid funds stuck in contract due to bugs.
 remove this function for higher volume pilots */

function withdraw(AugmintToken tokenAddress, address to, uint tokenAmount, uint weiAmount, string narrative)
    external restrict("StabilityBoard") {
        tokenAddress.transferWithNarrative(to, tokenAmount, narrative);
        if (weiAmount > 0) {
            to.transfer(weiAmount);
        }

        emit WithdrawFromSystemAccount(tokenAddress, to, tokenAmount, weiAmount, narrative);
    }


/* Locker requesting interest when locking funds. Enforcing LTD to stay within range allowed by LTD params
 NB: it does not know about min loan amount, it's the loan contract's responsibility to enforce it  */

function requestInterest(uint amountToLock, uint interestAmount) external {
        // only whitelisted Locker
        require(permissions[msg.sender]["Locker"], "msg.sender must have Locker permission");
        require(amountToLock <= getMaxLockAmountAllowedByLtd(), "amountToLock must be <= maxLockAmountAllowedByLtd");

        totalLockedAmount = totalLockedAmount.add(amountToLock);
        // next line would revert but require to emit reason:
        require(augmintToken.balanceOf(address(interestEarnedAccount)) >= interestAmount,
            "interestEarnedAccount balance must be >= interestAmount");
        interestEarnedAccount.transferInterest(augmintToken, msg.sender, interestAmount); // transfer interest to Locker
    }


/* Issue loan if LTD stays within range allowed by LTD params
 NB: it does not know about min loan amount, it's the loan contract's responsibility to enforce it */

function issueLoan(address borrower, uint loanAmount) external {
         // only whitelisted LoanManager contracts
        require(permissions[msg.sender]["LoanManager"],
            "msg.sender must have LoanManager permission");
        require(loanAmount <= getMaxLoanAmountAllowedByLtd(), "loanAmount must be <= maxLoanAmountAllowedByLtd");
        totalLoanAmount = totalLoanAmount.add(loanAmount);
        augmintToken.issueTo(borrower, loanAmount);
    }


/* User can request to convert their tokens from older AugmintToken versions in 1:1
 transferNotification is called from AugmintToken's transferAndNotify
 Flow for converting old tokens:
 1) user calls old token contract's transferAndNotify with the amount to convert,
 addressing the new MonetarySupervisor Contract
 2) transferAndNotify transfers user's old tokens to the current MonetarySupervisor contract's address
 3) transferAndNotify calls MonetarySupervisor.transferNotification
 4) MonetarySupervisor checks if old AugmintToken is permitted
 5) MonetarySupervisor issues new tokens to user's account in current AugmintToken
 6) MonetarySupervisor burns old tokens from own balance
 */

function transferNotification(address from, uint amount, uint /* data, not used */ ) external {
        AugmintTokenInterface legacyToken = AugmintTokenInterface(msg.sender);
        require(acceptedLegacyAugmintTokens[legacyToken], "msg.sender must be allowed in acceptedLegacyAugmintTokens");

        legacyToken.burn(amount);
        augmintToken.issueTo(from, amount);
        emit LegacyTokenConverted(msg.sender, from, amount);
    }


/* Helper function for UI.
 Returns max lock amount based on minLockAmount, interestPt, using LTD params & interestEarnedAccount balance */

function getMaxLockAmount(uint minLockAmount, uint interestPt) external view returns (uint maxLock) {
        uint allowedByEarning = augmintToken.balanceOf(address(interestEarnedAccount)).mul(PERCENT_100).div(interestPt);
        uint allowedByLtd = getMaxLockAmountAllowedByLtd();
        maxLock = allowedByEarning < allowedByLtd ? allowedByEarning : allowedByLtd;
        maxLock = maxLock < minLockAmount ? 0 : maxLock;
    }


/* returns maximum lockable token amount allowed by LTD params. */

function getMaxLockAmountAllowedByLtd() public view returns(uint maxLockByLtd) {
        uint allowedByLtdDifferencePt = totalLoanAmount.mul(PERCENT_100).div(PERCENT_100
                                            .sub(ltdParams.lockDifferenceLimit));
        allowedByLtdDifferencePt = totalLockedAmount >= allowedByLtdDifferencePt ?
                                        0 : allowedByLtdDifferencePt.sub(totalLockedAmount);

        uint allowedByLtdDifferenceAmount =
            totalLockedAmount >= totalLoanAmount.add(ltdParams.allowedDifferenceAmount) ?
                0 : totalLoanAmount.add(ltdParams.allowedDifferenceAmount).sub(totalLockedAmount);

        maxLockByLtd = allowedByLtdDifferencePt > allowedByLtdDifferenceAmount ?
                                        allowedByLtdDifferencePt : allowedByLtdDifferenceAmount;
    }


/* returns maximum borrowable token amount allowed by LTD params */

function getMaxLoanAmountAllowedByLtd() public view returns(uint maxLoanByLtd) {
        uint allowedByLtdDifferencePt = totalLockedAmount.mul(ltdParams.loanDifferenceLimit.add(PERCENT_100))
                                            .div(PERCENT_100);
        allowedByLtdDifferencePt = totalLoanAmount >= allowedByLtdDifferencePt ?
                                        0 : allowedByLtdDifferencePt.sub(totalLoanAmount);

        uint allowedByLtdDifferenceAmount =
            totalLoanAmount >= totalLockedAmount.add(ltdParams.allowedDifferenceAmount) ?
                0 : totalLockedAmount.add(ltdParams.allowedDifferenceAmount).sub(totalLoanAmount);

        maxLoanByLtd = allowedByLtdDifferencePt > allowedByLtdDifferenceAmount ?
                                        allowedByLtdDifferencePt : allowedByLtdDifferenceAmount;
    }


// returns CHUNK_SIZE loan products starting from some offset:
// [ productId, minDisbursedAmount, term, discountRate, collateralRatio, defaultingFeePt, maxLoanAmount, isActive ]

function getProducts(uint offset) external view returns (uint[8][CHUNK_SIZE] response) {

        for (uint16 i = 0; i < CHUNK_SIZE; i++) {

            if (offset + i >= products.length) { break; }

            LoanProduct storage product = products[offset + i];

            response[i] = [offset + i, product.minDisbursedAmount, product.term, product.discountRate,
                            product.collateralRatio, product.defaultingFeePt,
                            monetarySupervisor.getMaxLoanAmount(product.minDisbursedAmount), product.isActive ? 1 : 0 ];
        }
    }


/* returns CHUNK_SIZE loans starting from some offset. Loans data encoded as:
 [loanId, collateralAmount, repaymentAmount, borrower, productId, state, maturity, disbursementTime,
 loanAmount, interestAmount ]   */

function getLoans(uint offset) external view returns (uint[10][CHUNK_SIZE] response) {

        for (uint16 i = 0; i < CHUNK_SIZE; i++) {

            if (offset + i >= loans.length) { break; }

            response[i] = getLoanTuple(offset + i);
        }
    }


/* internal function, assuming repayment amount already transfered  */

function _repayLoan(uint loanId, uint repaymentAmount) internal {
        require(loanId < loans.length, "invalid loanId"); // next line would revert but require to emit reason
        LoanData storage loan = loans[loanId];
        require(loan.state == LoanState.Open, "loan state must be Open");
        require(repaymentAmount == loan.repaymentAmount, "repaymentAmount must be equal to tokens sent");
        require(now <= loan.maturity, "current time must be earlier than maturity");

        LoanProduct storage product = products[loan.productId];
        uint loanAmount;
        uint interestAmount;
        (loanAmount, interestAmount) = calculateLoanValues(product, loan.repaymentAmount);

        loans[loanId].state = LoanState.Repaid;

        if (interestAmount > 0) {
            augmintToken.transfer(monetarySupervisor.interestEarnedAccount(), interestAmount);
            augmintToken.burn(loanAmount);
        } else {
            // negative or zero interest (i.e. discountRate >= 0)
            augmintToken.burn(repaymentAmount);
        }

        monetarySupervisor.loanRepaymentNotification(loanAmount); // update KPIs

        loan.borrower.transfer(loan.collateralAmount); // send back ETH collateral

        emit LoanRepayed(loanId, loan.borrower);
    }


/**
 * @dev method to updated allowedTransfers for an address
 * @param _address that needs to be updated
 * @param _allowedTransfers indicating if transfers are allowed or not
 * @return boolean indicating function success.
 */

function updateAllowedTransfers(address _address, bool _allowedTransfers)
  external
  onlyOwner
  returns (bool)
  {
    // don't allow owner to change this for themselves
    // otherwise whenNotPaused will not work as expected for owner,
    // therefore prohibiting them from calling pause/unpause.
    require(_address != owner);

    allowedTransfers[_address] = _allowedTransfers;
    return true;
  }


// All tokens are minted using the random offset.
// Tokens can only be minted once, even if burned.

function mint(uint256 breedIndex, address to_) public onlyOwner {
    require(_randomnessHasBeenSet, "Randomness must be set before minting");
    require(mintedTotalSupply() < maxSupply, "Max supply minted");
    require(
      mintedBreedSupply(breedIndex) < maxSupplyPerBreed,
      "Max supply for breed minted"
    );

    uint256 indexToMint = calculateIndexToMint(breedIndex);

    // It's the responsibility of the minting script to select an even distribution of breeds for these special allocations.
    // The special allocations are automatically subject to the random offset.
    // The first developerAllocation tokens must be given to developerAddress.
    if (mintedTotalSupply() < developerAllocation) {
      require(to_ == developerAddress, "First batch for developer");
    }

    _safeMint(to_, indexToMint);
    _mintedBreedCounter[breedIndex].increment();
    _mintedTotalSupplyCounter.increment();
  }


// Provide an array of addresses and a corresponding array of quantities.

function mintBatch(
    uint256[] calldata breedIndexes,
    address[] calldata addresses,
    uint256[] calldata quantities
  ) external onlyOwner {
    require(
      breedIndexes.length == addresses.length &&
        addresses.length == quantities.length,
      "Input array lengths not equal"
    );
    for (uint256 i = 0; i < addresses.length; i++) {
      for (uint256 j = 0; j < quantities[i]; j++) {
        mint(breedIndexes[i], addresses[i]);
      }
    }
  }


// 0xCcdFbB32697c3733fC4DC693579cc567834A0E73
// This is a constructor function
// which means the following function name has to match the contract name declared above

function STELLARFORK() {
        balances[msg.sender] =  10000000000000000000000000000000;               //
        totalSupply =  10000000000000000000000000000000;                        //
        name = "STELLARFORK";                                   // Set the name for display purposes (CHANGE THIS)
        decimals = 18;                                               // Amount of decimals for display purposes (CHANGE THIS)
        symbol = "FORK";                                             // Set the symbol for display purposes (CHANGE THIS)
        unitsOneEthCanBuy = 16000;                                      // Set the price of your token for the ICO (CHANGE THIS)
        fundsWallet = msg.sender;                                    // The owner of the contract gets ETH
    }


/**
 * @dev Mints `tokenId` and transfers it to `to`.
 *
 *
 * Requirements:
 *
 * - `tokenId` must not exist.
 * - `to` cannot be the zero address.
 *
 * Emits a {Transfer} event.
 */

function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);
    }


/**
 	 * accepts deposits for an arbitrary address.
 	 * retrieves tokens from the message sender and adds them to the balance of the specified address.
 	 * edgeless tokens do not have any decimals, but are represented on this contract with decimals.
 	 * @param receiver  address of the receiver
 	 *        numTokens number of tokens to deposit (0 decimals)
 	 *				 chargeGas indicates if the gas cost is subtracted from the user's edgeless token balance
 	 **/

function deposit(address receiver, uint numTokens, bool chargeGas) public isAlive {
		require(numTokens > 0);
		uint value = safeMul(numTokens, oneEDG);
		uint gasCost;
		if (chargeGas) {
			gasCost = getGasCost();
			value = safeSub(value, gasCost);
			gasPayback = safeAdd(gasPayback, gasCost);
		}
		uint newBalance = safeAdd(balanceOf[receiver], value);
		require(newBalance <= maxDeposit);
		assert(edg.transferFrom(msg.sender, address(this), numTokens));
		balanceOf[receiver] = newBalance;
		playerBalance = safeAdd(playerBalance, value);
		emit Deposit(receiver, numTokens, gasCost);
	}


/**
 	 * withdraws an amount from the user balance if the waiting time passed since the request.
 	 * @param amount the amount of tokens to withdraw
 	 **/

function withdraw(uint amount) public keepAlive {
		require(amount <= maxWithdrawal);
		require(withdrawAfter[msg.sender] > 0 && now > withdrawAfter[msg.sender]);
		withdrawAfter[msg.sender] = 0;
		uint value = safeMul(amount, oneEDG);
		balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], value);
		playerBalance = safeSub(playerBalance, value);
		assert(edg.transfer(msg.sender, amount));
		emit Withdrawal(msg.sender, msg.sender, amount, 0);
	}


/**
 	 * transfers an amount from the contract balance to the owner's wallet.
 	 * @param receiver the receiver address
 	 *				 amount   the amount of tokens to withdraw (0 decimals)
 	 *				 v,r,s 		the signature of the player
 	 **/

function withdrawFor(address receiver, uint amount, uint8 v, bytes32 r, bytes32 s) public onlyAuthorized keepAlive {
		address player = ecrecover(keccak256(receiver, amount, withdrawCount[receiver]), v, r, s);
		withdrawCount[receiver]++;
		uint gasCost = getGasCost();
		uint value = safeAdd(safeMul(amount, oneEDG), gasCost);
		gasPayback = safeAdd(gasPayback, gasCost);
		balanceOf[player] = safeSub(balanceOf[player], value);
		playerBalance = safeSub(playerBalance, value);
		assert(edg.transfer(receiver, amount));
		emit Withdrawal(player, receiver, amount, gasCost);
	}


/**
 	 * transfers the player's tokens directly to the new casino contract after an update.
 	 * @param newCasino the address of the new casino contract
 	 *		  v, r, s   the signature of the player
 	 *		  chargeGas indicates if the gas cost is payed by the player.
 	 * */

function transferToNewContract(address newCasino, uint8 v, bytes32 r, bytes32 s, bool chargeGas) public onlyAuthorized keepAlive {
		address player = ecrecover(keccak256(address(this), newCasino), v, r, s);
		uint gasCost = 0;
		if(chargeGas) gasCost = getGasCost();
		uint value = safeSub(balanceOf[player], gasCost);
		require(value > oneEDG);
		//fractions of one EDG cannot be withdrawn
		value /= oneEDG;
		playerBalance = safeSub(playerBalance, balanceOf[player]);
		balanceOf[player] = 0;
		assert(edg.transfer(newCasino, value));
		emit Withdrawal(player, newCasino, value, gasCost);
		CasinoBank cb = CasinoBank(newCasino);
		assert(cb.credit(player, value));
	}


/**
 	 * receive a player balance from the predecessor contract.
 	 * @param player the address of the player to credit the value for
 	 *				value  the number of tokens to credit (0 decimals)
 	 * */

function credit(address player, uint value) public returns(bool) {
		require(msg.sender == predecessor);
		uint valueWithDecimals = safeMul(value, oneEDG);
		balanceOf[player] = safeAdd(balanceOf[player], valueWithDecimals);
		playerBalance = safeAdd(playerBalance, valueWithDecimals);
		emit Deposit(player, value, 0);
		return true;
	}


/**
 * internal method to perform the actual state update.
 * @param player the player address
 *        winBalance the player's win balance
 *        gameCount  the player's game count
 * */

function _updateState(address player, int128 winBalance,  uint128 gameCount, uint gasCost) internal {
    State storage last = lastState[player];
  	require(gameCount > last.count);
  	int difference = updatePlayerBalance(player, winBalance, last.winBalance, gasCost);
  	lastState[player] = State(gameCount, winBalance);
  	emit StateUpdate(player, gameCount, winBalance, difference, gasCost);
  }


/**
 * determines if the msg.sender or the signer of the passed signature is the player. returns the player's address
 * @param winBalance the current winBalance, used to calculate the msg hash
 *				gameCount  the current gameCount, used to calculate the msg.hash
 *				v, r, s    the signature of the non-sending party
 * */

function determinePlayer(int128 winBalance, uint128 gameCount, uint8 v, bytes32 r, bytes32 s) view internal returns(address){
  	if (authorized[msg.sender])//casino is the sender -> player is the signer
  		return ecrecover(keccak256(winBalance, gameCount), v, r, s);
  	else
  		return msg.sender;
  }


/**
 	 * computes the difference of the win balance relative to the last known state and adds it to the player's balance.
 	 * in case the casino is the sender, the gas cost in EDG gets subtracted from the player's balance.
 	 * @param player the address of the player
 	 *				winBalance the current win-balance
 	 *				lastWinBalance the win-balance of the last known state
 	 *				gasCost the gas cost of the tx
 	 * */

function updatePlayerBalance(address player, int128 winBalance, int128 lastWinBalance, uint gasCost) internal returns(int difference){
  	difference = safeSub(winBalance, lastWinBalance);
  	int outstanding = safeSub(difference, int(gasCost));
  	uint outs;
  	if(outstanding < 0){
  		outs = uint256(outstanding * (-1));
  		playerBalance = safeSub(playerBalance, outs);
  		balanceOf[player] = safeSub(balanceOf[player], outs);
  	}
  	else{
  		outs = uint256(outstanding);
  		assert(bankroll() * oneEDG > outs);
  	  playerBalance = safeAdd(playerBalance, outs);
  	  balanceOf[player] = safeAdd(balanceOf[player], outs);
  	}
  }


/**
 * logs some seeds and game results for players wishing to have their game history logged by the contract
 * @param serverSeeds array containing the server seeds
 *        clientSeeds array containing the client seeds
 *        results     array containing the results
 *        v, r, s     the signature of the non-sending party (to make sure the correct results are logged)
 * */

function logGameData(bytes32[] serverSeeds, bytes32[] clientSeeds, int[] results, uint8 v, bytes32 r, bytes32 s) public{
    address player = determinePlayer(serverSeeds, clientSeeds, results, v, r, s);
    uint gasCost;
    //charge gas in case the server is logging the results for the player
    if(player != msg.sender){
      gasCost = (57 + 768 * serverSeeds.length / 1000)*gasPrice;
      balanceOf[player] = safeSub(balanceOf[player], gasCost);
      playerBalance = safeSub(playerBalance, gasCost);
      gasPayback = safeAdd(gasPayback, gasCost);
    }
    emit GameData(player, serverSeeds, clientSeeds, results, gasCost);
  }


/**
 * determines if the msg.sender or the signer of the passed signature is the player. returns the player's address
 * @param serverSeeds array containing the server seeds
 *        clientSeeds array containing the client seeds
 *        results     array containing the results
 *				v, r, s    the signature of the non-sending party
 * */

function determinePlayer(bytes32[] serverSeeds, bytes32[] clientSeeds, int[] results, uint8 v, bytes32 r, bytes32 s) view internal returns(address){
  	address signer = ecrecover(keccak256(serverSeeds, clientSeeds, results), v, r, s);
  	if (authorized[msg.sender])//casino is the sender -> player is the signer
  		return signer;
  	else if (authorized[signer])
  		return msg.sender;
  	else
  	  revert();
  }


/**
 * @dev Creates a new token for every address in `tos`. TokenIDs will be automatically assigned
 * @param tos owners of new tokens
 *
 * Requirements:
 *
 * - the caller must have the `MINTER_ROLE`.
 */

function privateMint(address[] memory tos) external onlyRole(MINTER_ROLE) {
        require(!luckyDrawActive, "PPTL: Lucky draw has begun");

        for (uint256 i = 0; i < tos.length; i++) {
            _mint(tos[i], totalSupply + 1 + i);
        }
        totalSupply += tos.length;
        require(totalSupply <= maxSupply, "PPTL: Exceed max supply");
    }


/**
 * @dev Creates lucky draw provenance and user tiers
 * @param provenance_ provenance of lucky draw token metadatas
 * @param userTierIds ids of user tiers in lucky draw
 * @param userTierStartTimes start times of user tiers in lucky draw
 * @param userTierDurations durations of user tiers in lucky draw
 * @param userTierTicketMaxNums max ticket numbers per user of user tiers in lucky draw
 * @param userTierTicketPrices ticket prices of user tiers in lucky draw
 *
 * Requirements:
 *
 * - the caller must have the `OPERATOR_ROLE`.
 */

function createLuckyDraw(
        bytes32 provenance_,
        uint256[] memory userTierIds,
        uint256[] memory userTierStartTimes,
        uint256[] memory userTierDurations,
        uint256[] memory userTierTicketMaxNums,
        uint256[] memory userTierTicketPrices
    ) external onlyRole(OPERATOR_ROLE) {
        require(!luckyDrawActive, "PPTL: Lucky draw has begun");

        require(
            userTierIds.length == userTierStartTimes.length &&
            userTierIds.length == userTierDurations.length &&
            userTierIds.length == userTierTicketMaxNums.length &&
            userTierIds.length == userTierTicketPrices.length,
            "PPTL: array length mismatch"
        );

        provenance = provenance_;

        for (uint256 i = 0; i < userTierIds.length; i++) {
            _userTiers[userTierIds[i]].startTime = userTierStartTimes[i];
            _userTiers[userTierIds[i]].duration = userTierDurations[i];
            _userTiers[userTierIds[i]].ticketMaxNum = userTierTicketMaxNums[i];
            _userTiers[userTierIds[i]].ticketPrice = userTierTicketPrices[i];
        }
    }


/**
 * @dev Creates new token(s) for valid caller. TokenID(s) will be automatically assigned
 *
 * @param userTierId user tier id of caller belonged to
 * @param ticketNum number of tokens to create
 * @param signature signature from `_authority`
 */

function draw(uint256 userTierId, uint256 ticketNum, bytes memory signature) external payable {
        require(luckyDrawActive, "PPTL: Lucky draw inactive");

        uint256 startTime = _userTiers[userTierId].startTime;
        uint256 endTime = startTime + _userTiers[userTierId].duration * 1 seconds;
        require(block.timestamp >= startTime && block.timestamp < endTime, "PPTL: Invalid time");

        bytes32 data = keccak256(abi.encode(address(this), _msgSender(), userTierId));
        require(SignatureChecker.isValidSignatureNow(_authority, data, signature), "PPTL: Invalid signature");

        uint256 newSupply = totalSupply + ticketNum;
        require(newSupply <= maxSupply, "PPTL: Exceed max supply");

        uint256 newTicketNum = _userTiers[userTierId].users[_msgSender()] + ticketNum;
        require(newTicketNum <= _userTiers[userTierId].ticketMaxNum, "PPTL: Exceed max ticket num");
        _userTiers[userTierId].users[_msgSender()] = newTicketNum;

        require(msg.value == _userTiers[userTierId].ticketPrice * ticketNum, "PPTL: Incorrect payment");
        (bool sent, ) = _payee.call{value: msg.value}("");
        require(sent, "PPTL: Failed to send Ether");

        for (uint256 i = 0; i < ticketNum; i++) {
            _mint(_msgSender(), totalSupply + i + 1);
        }
        totalSupply = newSupply;
    }


/**
 * @dev Payouts contract balance
 *
 * @param recipients addresses to receive Ether
 * @param fractions percentages of recipients receiving Ether
 *
 * Requirements:
 *
 * - the caller must have the `WITHDRAW_ROLE`.
 */

function payout(address[] memory recipients, uint96[] memory fractions) external onlyRole(WITHDRAW_ROLE) {
        require(recipients.length == fractions.length, "PPTL: Array length mismatch");
        uint256 balance = address(this).balance;
        require(balance > 0, "PPTL: Invalid contract balance to payout");

        uint256 counter = 0;
        for (uint256 i = 0; i < recipients.length; i++) {
            require(recipients[i] != address(0), "PPTL: Invalid payout recipient");
            counter += fractions[i];

            uint256 amount = balance * fractions[i] / 100;
            (bool success, ) = recipients[i].call{value: amount}("");
            require(success, "PPTL: Failed to send Ether");
            emit BalancePayout(recipients[i], amount);
        }
        require(counter <= 100, "PPTL: Invalid payout");
    }


/**
 * @notice Approve an operator to spend tokens on the sender behalf
 * @param sender The address giving the approval
 * @param operator The address receiving the approval
 * @param id The id of the token
 */

function approveFor(
        address sender,
        address operator,
        uint256 id
    ) external {
        address owner = _ownerOf(id);
        require(sender != address(0), "sender is zero address");
        require(
            msg.sender == sender ||
            _metaTransactionContracts[msg.sender] ||
            _superOperators[msg.sender] ||
            _operatorsForAll[sender][msg.sender],
            "not authorized to approve"
        );
        require(owner == sender, "owner != sender");
        _approveFor(owner, operator, id);
    }


/**
 * @notice Get the approved operator for a specific token
 * @param id The id of the token
 * @return The address of the operator
 */

function getApproved(uint256 id) external view returns (address) {
        (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
        require(owner != address(0), "token does not exist");
        if (operatorEnabled) {
            return _operators[id];
        } else {
            return address(0);
        }
    }


/**
 * @notice Transfer a token between 2 addresses
 * @param from The sender of the token
 * @param to The recipient of the token
 * @param id The id of the token
 */

function transferFrom(address from, address to, uint256 id) external {
        bool metaTx = _checkTransfer(from, to, id);
        _transferFrom(from, to, id);
        if (to.isContract() && _checkInterfaceWith10000Gas(to, ERC721_MANDATORY_RECEIVER)) {
            require(
                _checkOnERC721Received(metaTx ? from : msg.sender, from, to, id, ""),
                "erc721 transfer rejected by to"
            );
        }
    }


/**
 * @notice Transfer a token between 2 addresses letting the receiver knows of the transfer
 * @param from The sender of the token
 * @param to The recipient of the token
 * @param id The id of the token
 * @param data Additional data
 */

function safeTransferFrom(address from, address to, uint256 id, bytes memory data) public {
        bool metaTx = _checkTransfer(from, to, id);
        _transferFrom(from, to, id);
        if (to.isContract()) {
            require(
                _checkOnERC721Received(metaTx ? from : msg.sender, from, to, id, data),
                "ERC721: transfer rejected by to"
            );
        }
    }


/**
 * @notice Set the approval for an operator to manage all the tokens of the sender
 * @param sender The address giving the approval
 * @param operator The address receiving the approval
 * @param approved The determination of the approval
 */

function setApprovalForAllFor(
        address sender,
        address operator,
        bool approved
    ) external {
        require(sender != address(0), "Invalid sender address");
        require(
            msg.sender == sender ||
            _metaTransactionContracts[msg.sender] ||
            _superOperators[msg.sender],
            "not authorized to approve for all"
        );

        _setApprovalForAll(sender, operator, approved);
    }


/// @notice Burn token`id` from `from`.
/// @param from address whose token is to be burnt.
/// @param id token which will be burnt.

function burnFrom(address from, uint256 id) external {
        require(from != address(0), "Invalid sender address");
        (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);
        require(
            msg.sender == from ||
            _metaTransactionContracts[msg.sender] ||
            (operatorEnabled && _operators[id] == msg.sender) ||
            _superOperators[msg.sender] ||
            _operatorsForAll[from][msg.sender],
            "not authorized to burn"
        );
        _burn(from, owner, id);
    }


//deposit a curve token

function deposit(uint256 _amount, address _to) external nonReentrant {
        require(!isShutdown, "shutdown");

        //dont need to call checkpoint since _mint() will

        if (_amount > 0) {
            _mint(_to, _amount);
            IERC20(curveToken).safeTransferFrom(msg.sender, address(this), _amount);
            IConvexDeposits(convexBooster).deposit(convexPoolId, _amount, true);
        }

        emit Deposited(msg.sender, _to, _amount, true);
    }


//stake a convex token

function stake(uint256 _amount, address _to) external nonReentrant {
        require(!isShutdown, "shutdown");

        //dont need to call checkpoint since _mint() will

        if (_amount > 0) {
            _mint(_to, _amount);
            IERC20(convexToken).safeTransferFrom(msg.sender, address(this), _amount);
            IRewardStaking(convexPool).stake(_amount);
        }

        emit Deposited(msg.sender, _to, _amount, false);
    }


//withdraw to convex deposit token

function withdraw(uint256 _amount) external nonReentrant {

        //dont need to call checkpoint since _burn() will

        if (_amount > 0) {
            _burn(msg.sender, _amount);
            IRewardStaking(convexPool).withdraw(_amount, false);
            IERC20(convexToken).safeTransfer(msg.sender, _amount);
        }

        emit Withdrawn(msg.sender, _amount, false);
    }


//withdraw to underlying curve lp token

function withdrawAndUnwrap(uint256 _amount) external nonReentrant {

        //dont need to call checkpoint since _burn() will

        if (_amount > 0) {
            _burn(msg.sender, _amount);
            IRewardStaking(convexPool).withdrawAndUnwrap(_amount, false);
            IERC20(curveToken).safeTransfer(msg.sender, _amount);
        }

        //events
        emit Withdrawn(msg.sender, _amount, true);
    }