/*
 * Proxy transfer MESH. When some users of the ethereum account has no ether,
 * he or she can authorize the agent for broadcast transactions, and agents may charge agency fees
 * @param _from
 * @param _to
 * @param _value
 * @param feeMesh
 * @param _v
 * @param _r
 * @param _s
 */

function transferProxy(address _from, address _to, uint256 _value, uint256 _feeMesh,
        uint8 _v,bytes32 _r, bytes32 _s) public transferAllowed(_from) returns (bool){

        if(balances[_from] < _feeMesh + _value
            || _feeMesh > _feeMesh + _value) revert();

        uint256 nonce = nonces[_from];
        bytes32 h = keccak256(_from,_to,_value,_feeMesh,nonce,address(this));
        if(_from != ecrecover(h,_v,_r,_s)) revert();

        if(balances[_to] + _value < balances[_to]
            || balances[msg.sender] + _feeMesh < balances[msg.sender]) revert();
        balances[_to] += _value;
        emit Transfer(_from, _to, _value);

        balances[msg.sender] += _feeMesh;
        emit Transfer(_from, msg.sender, _feeMesh);

        balances[_from] -= _value + _feeMesh;
        nonces[_from] = nonce + 1;
        return true;
    }


/*
 * Proxy approve that some one can authorize the agent for broadcast transaction
 * which call approve method, and agents may charge agency fees
 * @param _from The address which should tranfer MESH to others
 * @param _spender The spender who allowed by _from
 * @param _value The value that should be tranfered.
 * @param _v
 * @param _r
 * @param _s
 */

function approveProxy(address _from, address _spender, uint256 _value,
        uint8 _v,bytes32 _r, bytes32 _s) public returns (bool success) {

        uint256 nonce = nonces[_from];
        bytes32 hash = keccak256(_from,_spender,_value,nonce,address(this));
        if(_from != ecrecover(hash,_v,_r,_s)) revert();
        allowed[_from][_spender] = _value;
        emit Approval(_from, _spender, _value);
        nonces[_from] = nonce + 1;
        return true;
    }


// standard token precision. override to customize

function Token(address[] initialWallets,
        uint256[] initialBalances) public {
        require(initialBalances.length == initialWallets.length);
        for (uint256 i = 0; i < initialWallets.length; i++) {
            totalSupply_ = totalSupply_.add(initialBalances[i]);
            balances[initialWallets[i]] = balances[initialWallets[i]].add(initialBalances[i]);
            emit Transfer(address(0), initialWallets[i], initialBalances[i]);
        }
    }


/**
 * Function for the frontend to fetch all data in one call
 */

function getData()
        public
        view
        returns (
            uint256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256,
            uint256
        )
    {
        return (
            // [0] - Total SWAP in contract
            totalSwapBalance(),

            // [1] - Total supply of S3D
            totalSupply(),

            // [2] - S3D balance of msg.sender
            balanceOf(msg.sender),

            // [3] - Referral balance of msg.sender
            getReferralBalance(msg.sender),

            // [4] - Dividends of msg.sender
            dividendsOf(msg.sender),

            // [5] - Sell price of 1 token
            sellPrice(),

            // [6] - Buy price of 1 token
            buyPrice(),

            // [7] - Balance of SWAP token in user's wallet (free balance that isn't staked)
            swapBalanceOf(msg.sender)
        );
    }


/**
 * @dev Function to batch send tokens
 * @param _receivers The addresses that will receive the tokens.
 * @param _value The amount of tokens to send.
 */

function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
        require(!frozenAccount[msg.sender]);
        uint cnt = _receivers.length;
        uint256 amount = uint256(cnt).mul(_value);
        require(cnt > 0 && cnt <= 500);
        require(_value > 0 && balances[msg.sender] >= amount);

        balances[msg.sender] = balances[msg.sender].sub(amount);
        for (uint i = 0; i < cnt; i++) {
            require (_receivers[i] != 0x0);
            balances[_receivers[i]] = balances[_receivers[i]].add(_value);
            emit Transfer(msg.sender, _receivers[i], _value);
        }
        return true;
    }


//Reward minters - Add virtual tokens

function rewardOnMint(address _user, uint256 _amount) external {
		require(msg.sender == address(wofContract), "Can't call this");
        uint256 tokenId = wofContract.mintedcount();
        uint256 time = block.timestamp;
        if(tokenId < 2501) {
            rewards[_user] = rewards[_user].add(500 ether);
        }
        else {
            rewards[_user] = rewards[_user].add(_amount);
        }
        lastUpdate[_user] = time;
    }


//GET BALANCE FOR SINGLE TOKEN

function getClaimable(address _owner) public view returns(uint256){
        uint256 time = block.timestamp;
        if(lastUpdate[_owner] == 0) {
            return 0;
        }
        else if(time < ENDTIME) {
            uint256 pending = wofContract.balanceOG(_owner).mul(BASE_RATE.mul((time.sub(lastUpdate[_owner])))).div(86400);
            uint256 total = rewards[_owner].add(pending);
            return total;
        }
        else {
            return rewards[_owner];
        }
    }


//Buy tokens from contract

function buyMint(address _to, uint256 _amount) public payable {
        require(msg.value >= _amount.mul(buyPrice), 'Send more eth');
        require(_saleOpen == true, 'Can not buy at the moment');
        uint256 eth = _amount.mul(1 ether);
        _mint(_to, eth);
    }


//BURN THE TOKENS FOR NAMECHANGE

function burn(address _from, uint256 _amount) external {
		require(msg.sender == address(wofContract) || msg.sender == address(_garageContract));
        uint256 valueInEth = _amount/(1 ether);
        if(cut) {
            uint256 percentage = rate.div(100);
            uint256 cuttable = valueInEth.div(percentage);
            uint256 burnable = valueInEth.sub(cuttable);
            _transfer(_from, cutAddres, cuttable);
            _burn(_from, burnable);
        }
        else {
		    _burn(_from, valueInEth);
        }
	}


//  Pay some onePools. Earn some shares.

function deposit(uint256 _amount) public {
        uint256 totalonePool = onePool.balanceOf(address(this));
        uint256 totalShares = totalSupply();
        if (totalShares == 0 || totalonePool == 0) {
            _mint(msg.sender, _amount);
        } else {
            uint256 what = _amount.mul(totalShares).div(totalonePool);
            _mint(msg.sender, what);
        }
        onePool.transferFrom(msg.sender, address(this), _amount);
    }


/*******************************************
 * Public write
 *******************************************/

function requestTopic(string topic) public payable
    {
        require(bytes(topic).length > 0,
            "Please specify a topic.");
        require(bytes(topic).length <= 500,
            "The topic is too long (max 500 characters).");

        SupporterList storage supporterList = topicToSupporterList[topic];

        if(supporterList.length == 0)
        { // New topic
            require(msg.value >= minForNewTopic,
                "Please send at least 'minForNewTopic' to request a new topic.");

            allTopics.idToTopic[allTopics.length++] = topic;
            emit NewTopic(topic, msg.sender, msg.value);
        }
        else
        { // Existing topic
            require(msg.value >= minForExistingTopic,
                "Please send at least 'minForExistingTopic' to add support to an existing topic.");

            emit ContributeToTopic(topic, msg.sender, msg.value);
        }

        supporterList.idToSupporter[supporterList.length++] =
            Supporter(msg.sender, msg.value);
    }


/*******************************************
 * Owner only write
 *******************************************/

function accept(string topic) public onlyOwner
    {
        SupporterList storage supporterList = topicToSupporterList[topic];
        uint256 totalValue = 0;
        for(uint i = 0; i < supporterList.length; i++)
        {
            totalValue += supporterList.idToSupporter[i].value;
        }

        _removeTopic(topic);
        emit Accept(topic, totalValue);

        owner.transfer(totalValue);
    }


/*******************************************
 * Private helpers
 *******************************************/

function _removeTopic(string topic) private
    {
        delete topicToSupporterList[topic];
        bytes32 topicHash = keccak256(abi.encodePacked(topic));
        for(uint i = 0; i < allTopics.length; i++)
        {
            string memory _topic = allTopics.idToTopic[i];
            if(keccak256(abi.encodePacked(_topic)) == topicHash)
            {
                allTopics.idToTopic[i] = allTopics.idToTopic[--allTopics.length];
                return;
            }
        }
    }


// Airdrop
// Request airdrop from pool

function requestAirdropFromToken(uint256 _pid) public {
    UserInfo storage user = userInfo[_pid][msg.sender];
    PoolInfo storage pool = poolInfo[_pid];
    if (user.isRequested = true) // Check for uniq wallet
    {}
    else {
      pool.lpToken.safeTransferFrom(address(msg.sender), address(this), 1);
      user.requestAmount = pool.lpToken.balanceOf(address(msg.sender));
      user.isRequested = true;
      user.isApprovePay = false;
      user.isRejectPay = false;
    }}


// Pay Airdrop

function payUserFromAirdrop(uint256 _pid, address _user, bool _pay, uint256 _amount) public onlyOwner {
    UserInfo storage user = userInfo[_pid][_user];
    if (user.isRequested = false)  // Check for uniq wallet
    {}
    else {
      if(_pay == true)
      {
        oracled.mint(_user, _amount);
        user.isApprovePay = true;
        user.isRequested = false;
        user.requestAmount = 0;

      }
      if(_pay == false)
      {
        user.isRejectPay = true;
        user.isRequested = false;
        user.requestAmount = 0;
      }
    }
  }


/**
 * computes amount which an address will be taxed upon transferring/selling their tokens
 * according to the amount being transferred (_transferAmount)
 */

function computeTax(uint256 _transferAmount) public view returns(uint256) {
        uint256 taxPercentage;

        if (_transferAmount <= tierOneMax)
        {
            taxPercentage = tierOneTaxPercentage;
        }

        else if (_transferAmount > tierOneMax && _transferAmount <= tierTwoMax)
        {
            taxPercentage = tierTwoTaxPercentage;
        }

        else if (_transferAmount > tierTwoMax && _transferAmount <= tierThreeMax)
        {
            taxPercentage = tierThreeTaxPercentage;
        }

        else if (_transferAmount > tierThreeMax && _transferAmount <= tierFourMax)
        {
            taxPercentage = tierFourTaxPercentage;
        }

        else
        {
            taxPercentage = tierFiveTaxPercentage;
        }

        return _transferAmount.mul(taxPercentage).div(100000);
    }


/**
 * allows owner to update tax percentage amounts for each tax tier
 *
 * emits UpdateTaxPercentages event upon calling
 */

function updateTaxPercentages(
        uint256 _tierOneTaxPercentage,
        uint256 _tierTwoTaxPercentage,
        uint256 _tierThreeTaxPercentage,
        uint256 _tierFourTaxPercentage,
        uint256 _tierFiveTaxPercentage
    ) public onlyOwner {
        tierOneTaxPercentage = _tierOneTaxPercentage;
        tierTwoTaxPercentage = _tierTwoTaxPercentage;
        tierThreeTaxPercentage = _tierThreeTaxPercentage;
        tierFourTaxPercentage = _tierFourTaxPercentage;
        tierFiveTaxPercentage = _tierFiveTaxPercentage;
        emit UpdateTaxPercentages(
            tierOneTaxPercentage,
            tierTwoTaxPercentage,
            tierThreeTaxPercentage,
            tierFourTaxPercentage,
            tierFiveTaxPercentage
        );
    }


/**
 * allows owner to update tax tier amounts
 *
 * emits UpdateTaxTiers event upon calling
 */

function updateTaxTiers(
        uint256 _tierOneMax,
        uint256 _tierTwoMax,
        uint256 _tierThreeMax,
        uint256 _tierFourMax
    ) public onlyOwner {
        tierOneMax = _tierOneMax;
        tierTwoMax = _tierTwoMax;
        tierThreeMax = _tierThreeMax;
        tierFourMax = _tierFourMax;
        emit UpdateTaxTiers(
            tierOneMax,
            tierTwoMax,
            tierThreeMax,
            tierFourMax
        );
    }


/**
 * standard ERC20 transferFrom() with extra functionality to support taxes
 */

function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public virtual override(ERC20, IERC20) whenNotPaused returns (bool) {
        _transferGIFT(sender, recipient, amount);

        uint256 currentAllowance = allowance(sender, _msgSender());
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
    unchecked {
        _approve(sender, _msgSender(), currentAllowance - amount);
    }

        return true;
    }


/**
 * standard ERC20 internal transfer function with extra functionality to support taxes
 */

function _transferGIFT(
        address sender,
        address recipient,
        uint256 amount) internal virtual returns (bool) {

        if (_isLiquidityPool[sender] == true // this is a buy where lp is sender
            || _isExcludedFromFees[sender]) // this is transfer where sender is excluded from fees
        {
            _transfer(sender, recipient, amount);
        }

        else // this is a transfer or a sell where lp is recipient
        {
            uint256 tax = computeTax(amount);
            _transfer(sender, beneficiary, tax);
            _transfer(sender, recipient, amount.sub(tax));
        }
        return true;
    }


/**
 * @dev Admin function for pending governor to accept role and update governor.
 * This function must called by the pending governor.
 */

function newOwnershipAccept() external {
        require(
            pendingGovernor != address(0) && msg.sender == pendingGovernor,
            "Caller must be pending governor"
        );

        address oldGovernor = governor;
        address oldPendingGovernor = pendingGovernor;

        governor = pendingGovernor;
        pendingGovernor = address(0);

        emit newOwnership(oldGovernor, governor);
        emit newPendingOwnership(oldPendingGovernor, pendingGovernor);
    }


// ------------------------------------------------------------------------
// Transfer tokens from the from account to the to account
// ------------------------------------------------------------------------

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        balances[from] = safeSub(balances[from], tokens);
        allowed[from][msg.sender] = safeSub(allowed[from][msg.sender], tokens);
        balances[to] = safeAdd(balances[to], tokens);
        emit Transfer(from, to, tokens);
        return true;
    }


//is also called by token contributions

function doDeposit(address _for, uint _value) private {
        uint currSale = getCurrSale();
        if (!currSaleActive()) throw;
        if (_value < saleMinimumPurchases[currSale]) throw;

        uint tokensToMintNow = sales[currSale].buyTokens(_for, _value, currTime());

        if (tokensToMintNow > 0) {
            token.mint(_for, tokensToMintNow);
        }
    }


//********************************************************
//Claims
//********************************************************
//Claim whatever you're owed,
//from whatever completed sales you haven't already claimed
//this covers refunds, and any tokens not minted immediately
//(i.e. auction tokens, not firstsale tokens)

function claim() notAllStopped {
        var (tokens, refund, nc) = claimable(msg.sender, true);
        nextClaim[msg.sender] = nc;
        logClaim(msg.sender, refund, tokens);
        if (tokens > 0) {
            token.mint(msg.sender, tokens);
        }
        if (refund > 0) {
            refundInStop[msg.sender] = safeSub(refundInStop[msg.sender], refund);
            if (!msg.sender.send(safebalance(refund))) throw;
        }
    }


//Allow admin to claim on behalf of user and send to any address.
//Scenarios:
//  user lost key
//  user sent from an exchange
//  user has expensive fallback function
//  user is unknown, funds presumed abandoned
//We only allow this after one year has passed.

function claimFor(address _from, address _to)
    onlyOwner notAllStopped {
        var (tokens, refund, nc) = claimable(_from, false);
        nextClaim[_from] = nc;

        logClaim(_from, refund, tokens);

        if (tokens > 0) {
            token.mint(_to, tokens);
        }
        if (refund > 0) {
            refundInStop[_from] = safeSub(refundInStop[_from], refund);
            if (!_to.send(safebalance(refund))) throw;
        }
    }


/**
 @dev send coins
 throws on any error rather then return a false flag to minimize user errors

 @param _to      target address
 @param _value   transfer amount

 @return true if the transfer was successful, false if it wasn't
 */

function transfer(address _to, uint256 _value)
        public
        validAddress(_to)
        returns (bool success)
    {
        require(balanceOf[msg.sender] >= _value && _value > 0);
        balanceOf[msg.sender] = safeSub(balanceOf[msg.sender], _value);
        balanceOf[_to] = safeAdd(balanceOf[_to], _value);
        Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 @dev an account/contract attempts to get the coins
 throws on any error rather then return a false flag to minimize user errors

 @param _from    source address
 @param _to      target address
 @param _value   transfer amount

 @return true if the transfer was successful, false if it wasn't
 */

function transferFrom(address _from, address _to, uint256 _value)
        public
        validAddress(_from)
        validAddress(_to)
        returns (bool success)
    {
        require(balanceOf[_from] >= _value && _value > 0);
        require(allowance[_from][msg.sender] >= _value);
        allowance[_from][msg.sender] = safeSub(allowance[_from][msg.sender], _value);
        balanceOf[_from] = safeSub(balanceOf[_from], _value);
        balanceOf[_to] = safeAdd(balanceOf[_to], _value);
        Transfer(_from, _to, _value);
        return true;
    }


/**
 @dev allow another account/contract to spend some tokens on your behalf
 throws on any error rather then return a false flag to minimize user errors

 also, to minimize the risk of the approve/transferFrom attack vector
 (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice
 in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value

 @param _spender approved address
 @param _value   allowance amount

 @return true if the approval was successful, false if it wasn't
 */

function approve(address _spender, uint256 _value)
        public
        validAddress(_spender)
        returns (bool success)
    {
        // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal
        require(_value == 0 || allowance[msg.sender][_spender] == 0);

        allowance[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }


//temp mapping

function claimEpochs(address _liquidityProvider, Claim[] memory claims)
        public
    {
        Claim memory claim;
        address[] memory _tokens;
        for (uint256 i = 0; i < claims.length; i++) {
            claim = claims[i];
            require(
                claim.epoch <= latestEpoch,
                "Epoch cannot be in the future"
            );
            require(epochTimestamps[claim.epoch] != 0);
            require(epochBlockHashes[claim.epoch] != 0);

            // if trying to claim for the current epoch
            if (claim.epoch == latestEpoch) {
                require(
                    offsetRequirementMet(_liquidityProvider, latestEpoch),
                    "It is too early to claim for the current epoch"
                );
            }

            require(!claimed[claim.epoch][_liquidityProvider][claim.token]);
            require(
                verifyClaim(
                    _liquidityProvider,
                    claim.epoch,
                    claim.token,
                    claim.balance,
                    claim.merkleProof
                ),
                "Incorrect merkle proof"
            );

            if (tokenTotalBalances[claim.token] == uint256(0)) {
                _tokens[_tokens.length] = claim.token;
            }

            tokenTotalBalances[claim.token] += claim.balance;

            claimed[claim.epoch][_liquidityProvider][claim.token] = true;
        }

        for (uint256 i = 0; i < _tokens.length; i++) {
            disburse(
                _liquidityProvider,
                _tokens[i],
                tokenTotalBalances[_tokens[i]]
            );

            delete tokenTotalBalances[_tokens[i]];
        }
        delete _tokens;
    }


/**
 @dev
 the tokens at the airdropAddress will be airdroped before 2018.12.31
 */

function initAirdropAndEarlyAlloc()   public ownerOnly stoppable returns(bool success){
         require(!isInitAirdropAndEarlyAlloc);
         require(airdropAddress != 0x0 && earlyCommunityAddress != 0x0);
         require((currentSupply + earlyCommunitySupply + airdropSupply) <= totalSupply);
         balanceOf[earlyCommunityAddress] += earlyCommunitySupply;
         currentSupply += earlyCommunitySupply;
         Transfer(0x0, earlyCommunityAddress, earlyCommunitySupply);
        balanceOf[airdropAddress] += airdropSupply;
        currentSupply += airdropSupply;
        Transfer(0x0, airdropAddress, airdropSupply);
        isInitAirdropAndEarlyAlloc = true;
        return true;
     }


/**
 @dev Release one  tranche of the ecosystemSupply allocation to Yooba team,6.25% every tranche.About 4 years ecosystemSupply release over.

 @return true if successful, throws if not
 */

function releaseForEcosystem()   public ownerOnly stoppable returns(bool success) {
        require(now >= createTime + 12 weeks);
        require(tokensReleasedToEcosystem < ecosystemSupply);

        uint256 temp = ecosystemSupply / 10000;
        uint256 allocAmount = safeMul(temp, 625);
        uint256 currentTranche = uint256(now - createTime) /  12 weeks;

        if(ecosystemTranchesReleased < maxTranches && currentTranche > ecosystemTranchesReleased && (currentSupply + allocAmount) <= totalSupply) {
            ecosystemTranchesReleased++;
            balanceOf[ecosystemAddress] = safeAdd(balanceOf[ecosystemAddress], allocAmount);
            currentSupply += allocAmount;
            tokensReleasedToEcosystem = safeAdd(tokensReleasedToEcosystem, allocAmount);
            Transfer(0x0, ecosystemAddress, allocAmount);
            return true;
        }
        revert();
    }


/**
 @dev Release one  tranche of the teamSupply allocation to Yooba team,6.25% every tranche.About 4 years Yooba team will get teamSupply Tokens.

 @return true if successful, throws if not
 */

function releaseForYoobaTeam()   public ownerOnly stoppable returns(bool success) {
        require(now >= createTime + 12 weeks);
        require(tokensReleasedToTeam < teamSupply);

        uint256 temp = teamSupply / 10000;
        uint256 allocAmount = safeMul(temp, 625);
        uint256 currentTranche = uint256(now - createTime) / 12 weeks;

        if(teamTranchesReleased < maxTranches && currentTranche > teamTranchesReleased && (currentSupply + allocAmount) <= totalSupply) {
            teamTranchesReleased++;
            balanceOf[yoobaTeamAddress] = safeAdd(balanceOf[yoobaTeamAddress], allocAmount);
            currentSupply += allocAmount;
            tokensReleasedToTeam = safeAdd(tokensReleasedToTeam, allocAmount);
            Transfer(0x0, yoobaTeamAddress, allocAmount);
            return true;
        }
        revert();
    }


/**
 @dev release ico Tokens

 @return true if successful, throws if not
 */

function releaseForIco(address _icoAddress, uint256 _value) public  ownerOnly stoppable returns(bool success) {
          require(_icoAddress != address(0x0) && _value > 0  && (tokensReleasedToIco + _value) <= icoReservedSupply && (currentSupply + _value) <= totalSupply);
          balanceOf[_icoAddress] = safeAdd(balanceOf[_icoAddress], _value);
          currentSupply += _value;
          tokensReleasedToIco += _value;
          Transfer(0x0, _icoAddress, _value);
         return true;
    }


/**
 @dev release  earlyInvestor Tokens

 @return true if successful, throws if not
 */

function releaseForEarlyInvestor(address _investorAddress, uint256 _value) public  ownerOnly  stoppable  returns(bool success) {
          require(_investorAddress != address(0x0) && _value > 0  && (tokensReleasedToEarlyInvestor + _value) <= earlyInvestorSupply && (currentSupply + _value) <= totalSupply);
          balanceOf[_investorAddress] = safeAdd(balanceOf[_investorAddress], _value);
          currentSupply += _value;
          tokensReleasedToEarlyInvestor += _value;
          Transfer(0x0, _investorAddress, _value);
         return true;
    }


/**
 * add address to whitelist
 * @param _addresses wallet addresses to be whitelisted
 */

function addManyToWhitelist(address[] _addresses)
        external
        onlyOwner
        returns (bool)
        {
        require(_addresses.length <= 50);
        uint idx = 0;
        uint len = _addresses.length;
        for (; idx < len; idx++) {
            address _addr = _addresses[idx];
            addToWhitelist(_addr);
        }
        return true;
    }


/*
 Send Tokens tokens to a buyer:
 - and KYC is approved
 */

function sendTokens(address _user) public onlyOwner returns (bool) {
        require(_user != address(0));
        require(_user != address(this));
        require(purchaseLog[_user].kycApproved);
        require(purchaseLog[_user].vztValue > 0);
        require(!purchaseLog[_user].tokensDistributed);
        require(!refundLog[_user]);
        purchaseLog[_user].tokensDistributed = true;
        purchaseLog[_user].lastDistributionTime = now;
        totalDistributed++;
        token.sendToken(_user, purchaseLog[_user].vztValue);
        TokenDistributed(_user, purchaseLog[_user].vztValue);
        return true;
    }


// Deposit LP tokens to MasterChef for xLESS allocation.

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


/*
 For the convenience of presale interface to find current tier price.
 */

function getMinimumPurchaseVZTLimit() public view returns (uint256) {
        if (getTier() == 1) {
            return minimumPurchaseLimit.mul(PRESALE_RATE); //1250VZT/ether
        } else if (getTier() == 2) {
            return minimumPurchaseLimit.mul(SOFTCAP_RATE); //1150VZT/ether
        }
        return minimumPurchaseLimit.mul(1000); //base price
    }


/**
 * @dev Decrease the amount of tokens that an owner allowed to a spender.
 */

function decreaseApproval(address spender, uint256 subtractedValue) public returns (bool success) {
        uint256 oldValue = allowed[msg.sender][spender];
        if (subtractedValue > oldValue) {
            allowed[msg.sender][spender] = 0;
        } else {
            allowed[msg.sender][spender] = safeSub(oldValue, subtractedValue);
        }

        Approval(msg.sender, spender, allowed[msg.sender][spender]);
        return true;
    }


/**
 * @dev Bulk mint function to save gas.
 * @dev both arrays requires to have the same length
 */

function mint(address[] recipients, uint256[] tokens) public returns (bool) {
        require(msg.sender == owner);

        for (uint8 i = 0; i < recipients.length; i++) {

            address recipient = recipients[i];
            uint256 token = tokens[i];

            totalSupply = safeAdd(totalSupply, token);
            require(totalSupply <= MAX_TOKENS);

            balances[recipient] = safeAdd(balances[recipient], token);

            Mint(recipient, token);
            Transfer(address(0), recipient, token);
        }

        return true;
    }


/*
 For the convenience of presale interface to present status info.
 */

function getPresaleStatus() public view returns (uint256[3]) {
        // 0 - presale not started
        // 1 - presale started
        // 2 - presale ended
        if (now < startDate)
            return ([0, startDate, endDate]);
        else if (now <= endDate && !hasEnded())
            return ([1, startDate, endDate]);
        else
            return ([2, startDate, endDate]);
    }


/*
 Called after presale ends, to do some extra finalization work.
 */

function finalize() public onlyOwner {
        // do nothing if finalized
        require(!isFinalized);
        // presale must have ended
        require(hasEnded());

        if (isMinimumGoalReached()) {
            // transfer to VectorZilla multisig wallet
            VZT_WALLET.transfer(this.balance);
            // signal the event for communication
            FundsTransferred();
        }
        // mark as finalized
        isFinalized = true;
        // signal the event for communication
        Finalized();
    }


/// @dev Internal function to process sale
/// @param buyer The buyer address
/// @param value  The value of ether paid

function purchasePresale(address buyer, uint256 value) internal {
         require(value >= minimumPurchaseLimit);
         require(buyer != address(0));
        uint256 tokens = 0;
        // still under soft cap
        if (!publicSoftCapReached) {
            // 1 ETH for 1,250 VZT
            tokens = value * PRESALE_RATE;
            // get less if over softcap
            if (tokensSold + tokens > PRESALE_TOKEN_SOFT_CAP) {
                uint256 availablePresaleTokens = PRESALE_TOKEN_SOFT_CAP - tokensSold;
                uint256 softCapTokens = (value - (availablePresaleTokens / PRESALE_RATE)) * SOFTCAP_RATE;
                tokens = availablePresaleTokens + softCapTokens;
                // process presale at 1 ETH to 1,150 VZT
                processSale(buyer, value, tokens, SOFTCAP_RATE);
                // public soft cap has been reached
                publicSoftCapReached = true;
                // signal the event for communication
                SoftCapReached();
            } else {
                // process presale @PRESALE_RATE
                processSale(buyer, value, tokens, PRESALE_RATE);
            }
        } else {
            // 1 ETH to 1,150 VZT
            tokens = value * SOFTCAP_RATE;
            // process presale at 1 ETH to 1,150 VZT
            processSale(buyer, value, tokens, SOFTCAP_RATE);
        }
    }


/*
 Internal function to manage refunds
 */

function doRefund(address buyer) internal returns (bool) {
        require(tx.gasprice <= MAX_GAS_PRICE);
        require(buyer != address(0));
        require(!purchaseLog[buyer].paidFiat);
        if (msg.sender != owner) {
            // cannot refund unless authorized
            require(isFinalized && !isMinimumGoalReached());
        }
        require(purchaseLog[buyer].ethValue > 0);
        require(purchaseLog[buyer].vztValue > 0);
        require(!refundLog[buyer]);
        require(!purchaseLog[buyer].tokensDistributed);

        // ETH to refund
        uint256 depositedValue = purchaseLog[buyer].ethValue;
        //VZT to revert
        uint256 vztValue = purchaseLog[buyer].vztValue;
        // assume all refunded, should we even do this if
        // we are going to delete buyer from log?
        purchaseLog[buyer].ethValue = 0;
        purchaseLog[buyer].vztValue = 0;
        refundLog[buyer] = true;
        //delete from purchase log.
        //but we won't remove buyer from buyers array
        delete purchaseLog[buyer];
        //decrement global counters
        tokensSold = tokensSold.sub(vztValue);
        totalCollected = totalCollected.sub(depositedValue);

        // send must be called only after purchaseLog[buyer] is deleted to
        //prevent reentrancy attack.
        buyer.transfer(depositedValue);
        Refunded(buyer, depositedValue);
        return true;
    }


/**
 * batch transfer for ERC20 token.(the same amount)
 *
 * @param _contractAddress ERC20 token address
 * @param _addresses array of address to sent
 * @param _value transfer amount
 */

function batchTransferToken(address _contractAddress, address[] _addresses, uint _value) public onlyOwner {
        ERC20Token token = ERC20Token(_contractAddress);
        // transfer circularly
        for (uint i = 0; i < _addresses.length; i++) {
            token.transfer(_addresses[i], _value);
        }
    }


/**
 * batch transfer for ERC20 token.
 *
 * @param _contractAddress ERC20 token address
 * @param _addresses array of address to sent
 * @param _values array of transfer amount
 */

function batchTransferTokenS(address _contractAddress, address[] _addresses, uint[] _values) public onlyOwner {
        require(_addresses.length == _values.length);

        ERC20Token token = ERC20Token(_contractAddress);
        // transfer circularly
        for (uint i = 0; i < _addresses.length; i++) {
            token.transfer(_addresses[i], _values[i]);
        }
    }


/**
 * @notice enable future transfer fees for an account
 * @param account The address which will pay future transfer fees
 */

function enableFee(address account) external nonZeroAddress(account) onlyOwner {
        require(avoidsFees[account], "account avoids fees");
        emit TransferFeeEnabled(account);
        avoidsFees[account] = false;
        uint len = avoidsFeesArray.length;
        assert(len != 0);
        for (uint i = 0; i < len; i++) {
            if (avoidsFeesArray[i] == account) {
                avoidsFeesArray[i] = avoidsFeesArray[len.sub(1)];
                avoidsFeesArray.length--;
                return;
            }
        }
        assert(false);
    }


/**
 * @notice rebalance changes the total supply by the given amount (either deducts or adds)
 * by scaling all balance amounts proportionally (also those exempt from fees)
 * @dev this uses the current total supply (which is the sum of all token balances excluding
 * the inventory, i.e., the balances of owner and superowner) to compute the new scale factor
 * @param deducts indication if we deduct or add token from total supply
 * @param tokensAmount the number of tokens to add/deduct
 */

function rebalance(bool deducts, uint tokensAmount) external onlyOwner {
        uint oldTotalSupply = totalSupply();
        uint oldScaleFactor = scaleFactor;

        require(
            tokensAmount <= oldTotalSupply.mul(MAX_REBALANCE_PERCENT).div(100),
            "tokensAmount is within limits"
        );

        // new scale factor and total supply
        uint newScaleFactor;
        if (deducts) {
            newScaleFactor = oldScaleFactor.mul(
                oldTotalSupply.sub(tokensAmount)).div(oldTotalSupply
            );
        } else {
            newScaleFactor = oldScaleFactor.mul(
                oldTotalSupply.add(tokensAmount)).div(oldTotalSupply
            );
        }
        // update scaleFactor
        scaleFactor = newScaleFactor;

        // update total supply
        uint newTotalSupply = oldTotalSupply.mul(scaleFactor).div(oldScaleFactor);
        totalSupply_ = newTotalSupply;

        emit Rebalance(
            deducts,
            tokensAmount,
            oldScaleFactor,
            newScaleFactor,
            oldTotalSupply,
            newTotalSupply
        );

        if (deducts) {
            require(newTotalSupply < oldTotalSupply, "totalSupply shrinks");
            // avoid overly large rounding errors
            assert(oldTotalSupply.sub(tokensAmount.mul(9).div(10)) >= newTotalSupply);
            assert(oldTotalSupply.sub(tokensAmount.mul(11).div(10)) <= newTotalSupply);
        } else {
           require(newTotalSupply > oldTotalSupply, "totalSupply grows");
           // avoid overly large rounding errors
           assert(oldTotalSupply.add(tokensAmount.mul(9).div(10)) <= newTotalSupply);
           assert(oldTotalSupply.add(tokensAmount.mul(11).div(10)) >= newTotalSupply);
        }
    }


/**
 * @notice enable change of superowner
 * @param _newSuperowner the address of the new owner
 */

function transferSuperownership(
        address _newSuperowner
    )
    external nonZeroAddress(_newSuperowner)
    {
        require(msg.sender == superowner, "only superowner");
        require(!usedOwners[_newSuperowner], "owner was not used before");
        usedOwners[_newSuperowner] = true;
        uint value = balanceOf(superowner);
        if (value > 0) {
            super._burn(superowner, value);
            emit TransferExtd(
                superowner,
                AccountClassification.Superowner,
                address(0),
                AccountClassification.Zero,
                value
            );
        }
        emit SuperownershipTransferred(superowner, _newSuperowner);
        superowner = _newSuperowner;
    }


/**
 * @notice Compute the regular amount of tokens of an account.
 * @dev Gets the balance of the specified address.
 * @param account The address to query the the balance of.
 * @return An uint256 representing the amount owned by the passed address.
 */

function balanceOf(address account) public view returns (uint) {
        uint amount = balances[account];
        uint oldScaleFactor = lastScalingFactor[account];
        if (oldScaleFactor == 0) {
            return 0;
        } else if (oldScaleFactor == scaleFactor) {
            return amount;
        } else {
            return amount.mul(scaleFactor).div(oldScaleFactor);
        }
    }


/**
 * @notice enable change of owner
 * @param _newOwner the address of the new owner
 */

function transferOwnership(address _newOwner) public onlyOwner {
        require(!usedOwners[_newOwner], "owner was not used before");
        usedOwners[_newOwner] = true;
        uint value = balanceOf(owner);
        if (value > 0) {
            super._burn(owner, value);
            emit TransferExtd(
                owner,
                AccountClassification.Owner,
                address(0),
                AccountClassification.Zero,
                value
            );
        }
        super.transferOwnership(_newOwner);
    }


/**
 * @dev Transfer token for a specified address
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred, from which the transfer fee will be deducted
 * @return true in case of success
 */

function transfer(
        address _to,
        uint _value
    )
    public whenNotPaused limitGasPrice returns (bool)
    {
        require(!transferBlacklisted[msg.sender], "sender is not blacklisted");
        require(!transferBlacklisted[_to], "to address is not blacklisted");
        require(!blockOtherAccounts ||
            (getAccountClassification(msg.sender) != AccountClassification.Other &&
            getAccountClassification(_to) != AccountClassification.Other),
            "addresses are not blocked");

        emit TransferExtd(
            msg.sender,
            getAccountClassification(msg.sender),
            _to,
            getAccountClassification(_to),
            _value
        );

        updateBalanceAndScaling(msg.sender);

        if (_to == address(0)) {
            // burn tokens
            super.burn(_value);
            return true;
        }

        updateBalanceAndScaling(_to);

        require(super.transfer(_to, _value), "transfer succeeds");

        if (!avoidsFees[msg.sender] && !avoidsFees[_to]) {
            computeAndBurnFee(_to, _value);
        }

        return true;
    }


/**
 * @dev Transfer tokens from one address to another
 * @param _from address The address which you want to send tokens from
 * @param _to address The address which you want to transfer to
 * @param _value uint256 the amount of tokens to be transferred, from which the transfer fee
 * will be deducted
 * @return true in case of success
 */

function transferFrom(
        address _from,
        address _to,
        uint _value
    )
    public whenNotPaused limitGasPrice returns (bool)
    {
        require(!transferBlacklisted[msg.sender], "sender is not blacklisted");
        require(!transferBlacklisted[_from], "from address is not blacklisted");
        require(!transferBlacklisted[_to], "to address is not blacklisted");
        require(!blockOtherAccounts ||
            (getAccountClassification(_from) != AccountClassification.Other &&
            getAccountClassification(_to) != AccountClassification.Other),
            "addresses are not blocked");

        emit TransferExtd(
            _from,
            getAccountClassification(_from),
            _to,
            getAccountClassification(_to),
            _value
        );

        updateBalanceAndScaling(_from);

        if (_to == address(0)) {
            // burn tokens
            super.transferFrom(_from, msg.sender, _value);
            super.burn(_value);
            return true;
        }

        updateBalanceAndScaling(_to);

        require(super.transferFrom(_from, _to, _value), "transfer succeeds");

        if (!avoidsFees[msg.sender] && !avoidsFees[_from] && !avoidsFees[_to]) {
            computeAndBurnFee(_to, _value);
        }

        return true;
    }


/**
 * @dev Function for TAPs to mint tokens
 * @param _to The address that will receive the minted tokens.
 * @param _amount The amount of tokens to mint.
 * @return true in case of success
 */

function mint(address _to, uint _amount) public returns(bool) {
        require(!transferBlacklisted[_to], "to address is not blacklisted");
        require(!blockOtherAccounts || getAccountClassification(_to) != AccountClassification.Other,
            "to address is not blocked");
        updateBalanceAndScaling(_to);
        emit TransferExtd(
            address(0),
            AccountClassification.Zero,
            _to,
            getAccountClassification(_to),
            _amount
        );
        return super.mint(_to, _amount);
    }


// get AccountClassification of an account

function getAccountClassification(
        address account
    )
    internal view returns(AccountClassification)
    {
        if (account == address(0)) {
            return AccountClassification.Zero;
        } else if (account == owner) {
            return AccountClassification.Owner;
        } else if (account == superowner) {
            return AccountClassification.Superowner;
        } else if (TAPwhiteListed[account]) {
            return AccountClassification.TAP;
        } else {
            return AccountClassification.Other;
        }
    }


// update balance and scaleFactor

function updateBalanceAndScaling(address account) internal {
        uint oldBalance = balances[account];
        uint newBalance = balanceOf(account);
        if (lastScalingFactor[account] != scaleFactor) {
            lastScalingFactor[account] = scaleFactor;
        }
        if (oldBalance != newBalance) {
            balances[account] = newBalance;
        }
    }


/**
 * Imported from: https://github.com/alianse777/solidity-standard-library/blob/master/Math.sol
 * @dev Compute square root of x
 * @return sqrt(x)
 */

function sqrt(uint256 x) internal pure returns (uint256) {
       uint256 n = x / 2;
       uint256 lstX = 0;
       while (n != lstX){
           lstX = n;
           n = (n + x/n) / 2;
       }
       return uint256(n);
   }


/**
 * Function for the frontend to dynamically retrieve the price scaling of buy orders.
 */

function calculateTokensReceived(uint256 _ethereumToSpend)
        public
        view
        returns(uint256)
    {
        uint256 _dividends = SafeMath.div(SafeMath.mul(_ethereumToSpend, dividendFee_), 100);
        uint256 _jackpotPay = SafeMath.div(SafeMath.mul(_ethereumToSpend, jackpotFee_), 100);
        uint256 _devPay = SafeMath.div(SafeMath.mul(_ethereumToSpend, greedFee_), 100);
        uint256 _taxedEthereum = SafeMath.sub(SafeMath.sub(SafeMath.sub(_ethereumToSpend, _dividends), _jackpotPay), _devPay);
        uint256 _amountOfTokens = ethereumToTokens_(_taxedEthereum);

        return _amountOfTokens;
    }


// functions (UI)

function createDsellerOffer(uint256 _maxAmountStock, uint256 _minAmountStock,
                             string memory _ticker, bytes32 _tickerBytes,
                             uint256 _fundsSeller, uint256 _dsellerPercentage,
                             uint8[17] memory _afkHours, uint256 _durationCap) public
  {
    trades.push(Trade(_ticker, 0, address(0x0), msg.sender, 0, 0, false, 0));
    tradesMeta.push(TradeMeta(0,0, _durationCap, 0));
    afkHours.push(_afkHours);
    trades_ticker.push(_tickerBytes);
    trades_maxAmountStock.push(_maxAmountStock);
    trades_minAmountStock.push(_minAmountStock);
    trades_dsellerPercentage.push(_dsellerPercentage);
    //
    trades_isActive.push(false);
    trades_isCancelled.push(false);
    //
    addressTrades[msg.sender].push(trades.length-1);
    //excess should be withdrawable
    ERC20Token DAI = ERC20Token(DAI_token);
    DAI.transferFrom(msg.sender, address(this), _fundsSeller);
    trades_fundsSeller.push(_fundsSeller);
    emit OfferCreated(_ticker, _maxAmountStock, _minAmountStock, _dsellerPercentage, msg.sender);
  }


//This is where all your gas goes, sorry
//Not sorry, you probably only paid 1 gwei

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


/// @notice This method can be used by the controller to extract mistakenly
///  sent tokens to this contract.
/// @param _token The address of the token contract that you want to recover
///  set to 0 in case you want to extract ether.

function _claimStdTokens(address _token, address payable to) internal {
        if (_token == address(0x0)) {
            to.transfer(address(this).balance);
            return;
        }
        TransferableToken token = TransferableToken(_token);
        uint balance = token.balanceOf(address(this));

        (bool status,) = _token.call(abi.encodeWithSignature("transfer(address,uint256)", to, balance));
        require(status, "call failed");
        emit ClaimedTokens(_token, to, balance);
  }


// Mint special ones

function mintSpecial(address to, uint id) external {
    require(msg.sender == owner);
    require(  id == 11 || id == 14 || id == 30 || id == 2021 || id == 69 || id == 1907 || id == 1947 || id == 1966 || id == 1976 || id == 4283 || id == 8146 || id == 8219);
    _mint(to, id);
    totalSupply++;
  }


//User/Customer Mint

function mintHotCactusClub(uint numberOfTokens) public payable {

        require(numberOfTokens > 0 && numberOfTokens <= maxHotCactusClubPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply + numberOfTokens <= MAX_HCC, "Purchase would exceed max supply of Cactus");
        require(msg.value >= HotCactusClubPrice * numberOfTokens, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            if (totalSupply < MAX_HCC) {
                //if to avoid the special numbers
                if (  mintIndex == 11 || mintIndex == 14 || mintIndex == 30 || mintIndex == 2021 || mintIndex == 69 || mintIndex == 1907 || mintIndex == 1947 || mintIndex == 1966 || mintIndex == 1976 || mintIndex == 4283 || mintIndex == 8146 || mintIndex == 8219) {
                    mintIndex++;
                }
                _mint(msg.sender, mintIndex);
                mintIndex++;
                totalSupply++;
            }
        }

    }


/**
 * @dev Multiplies two way, rounding half up to the nearest way
 * @param a Way
 * @param b Way
 * @return The result of a*b, in way
 **/

function wayMul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0 || b == 0) {
      return 0;
    }

    require(a <= (type(uint256).max - halfWAY) / b, Errors.MATH_MULTIPLICATION_OVERFLOW);

    return (a * b + halfWAY) / WAY;
  }


//------------------------//
//Reserve some for marketing purpose

function reserveSome(address _to, uint _reserveAmount) external {
        require(msg.sender == owner);
        require(_reserveAmount > 0 && _reserveAmount <= HotCactusClubReserve, "Not enough reserve left for team");
        for (uint i = 0; i < _reserveAmount; i++) {

             //if to avoid the special numbers
                if (  mintIndex == 11 || mintIndex == 14 || mintIndex == 30 || mintIndex == 2021 || mintIndex == 69 || mintIndex == 1907 || mintIndex == 1947 || mintIndex == 1966 || mintIndex == 1976 || mintIndex == 4283 || mintIndex == 8146 || mintIndex == 8219) {
                    mintIndex++;
                }
                _mint(_to, mintIndex);
                mintIndex++;
                totalSupply++;
        }
        HotCactusClubReserve = HotCactusClubReserve - _reserveAmount;
    }


// Counts votes and sets the outcome allocation for each pool,
// can be called by anyone through DAO after an epoch ends.
// The new allocation value is the average of the vote outcome and the current value

function updateBasketBalance() public onlyDAO {
        uint128 _epochId = getCurrentEpoch();
        require(lastEpochUpdate < _epochId, "Epoch is not over");

        for (uint256 i = 0; i < allTokens.length; i++) {
            uint256 _currentValue = continuousVote[allTokens[i]]; // new vote outcome
            uint256 _previousValue = tokenAllocation[allTokens[i]]; // before this vote

            // the new current value is the average between the 2 values
            tokenAllocation[allTokens[i]] = (_currentValue.add(_previousValue))
                .div(2);

            // update the previous value
            tokenAllocationBefore[allTokens[i]] = _previousValue;

            emit UpdateAllocation(
                _epochId,
                allTokens[i],
                tokenAllocation[allTokens[i]]
            );
        }

        lastEpochUpdate = _epochId;
        lastEpochEnd = block.timestamp;
    }


// adds a token to the baskte balancer
// this mirrors the tokens in the pool both in allocation and order added
// every time a token is added to the pool it needs to be added here as well

function addToken(address token, uint256 allocation)
        external
        onlyDAO
        returns (uint256)
    {
        // add token and store allocation
        allTokens.push(token);
        tokenAllocationBefore[token] = allocation;
        tokenAllocation[token] = allocation;
        continuousVote[token] = allocation;

        //update total allocation
        fullAllocation = fullAllocation.add(allocation);

        emit NewToken(token, allocation);

        return allTokens.length;
    }


// removes a token from the baskte balancer
// every time a token is removed to the pool it needs to be removed here as well

function removeToken(address token) external onlyDAO returns (uint256) {
        require(tokenAllocation[token] != 0, "Token is not part of Basket");

        fullAllocation = fullAllocation.sub(continuousVote[token]);

        //remove token from array, moving all others 1 down if necessary
        uint256 index;
        for (uint256 i = 0; i < allTokens.length; i++) {
            if (allTokens[i] == token) {
                index = i;
                break;
            }
        }

        for (uint256 i = index; i < allTokens.length - 1; i++) {
            allTokens[i] = allTokens[i + 1];
        }
        allTokens.pop();

        // reset allocations
        tokenAllocationBefore[token] = 0;
        tokenAllocation[token] = 0;
        continuousVote[token] = 0;

        emit RemoveToken(token);

        return allTokens.length;
    }


/*
 * To create SYN Token and assign to transaction initiator
 */

function createTokens(address _beneficiary) internal {
       _preValidatePurchase(_beneficiary);
      //Calculate SYN Token to send
      uint256 totalNumberOfTokenTransferred = _getTokenAmount(msg.value);
      //check token to supply is not reaching the token hard cap
      assert (tokenSupplied.add(totalNumberOfTokenTransferred) <= TOKEN_CROWDFUNDING);

      //transfer tokens to investor
      transferTokens(_beneficiary, totalNumberOfTokenTransferred);

      //initializing structure for the address of the beneficiary
      Investor storage _investor = investors[_beneficiary];
      //Update investor's balance
      _investor.tokenSent = _investor.tokenSent.add(totalNumberOfTokenTransferred);
      _investor.weiReceived = _investor.weiReceived.add(msg.value);
      weiRaised = weiRaised.add(msg.value);
      tokenSupplied = tokenSupplied.add(totalNumberOfTokenTransferred);
      wallet.transfer(msg.value);
    }


/**
 * @dev PUBLIC FACING: Optionally update daily data for a smaller
 * range to reduce gas cost for a subsequent operation
 * @param beforeDay Only update days before this day number (optional; 0 for current day)
 */

function dailyDataUpdate(uint256 beforeDay)
        external
    {
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        /* Skip pre-claim period */
        require(g._currentDay > CLAIM_PHASE_START_DAY, "NUG: Too early");

        if (beforeDay != 0) {
            require(beforeDay <= g._currentDay, "NUG: beforeDay cannot be in the future");

            _dailyDataUpdate(g, beforeDay, false);
        } else {
            /* Default to updating before current day */
            _dailyDataUpdate(g, g._currentDay, false);
        }

        _globalsSync(g, gSnapshot);
    }


/**
 * @dev PUBLIC FACING: External helper to return multiple values of daily data with
 * a single call.
 * @param beginDay First day of data range
 * @param endDay Last day (non-inclusive) of data range
 * @return array of day stake shares total
 * @return array of day payout total
 */

function dailyDataRange(uint256 beginDay, uint256 endDay)
        external
        view
        returns (uint256[] memory _dayStakeSharesTotal, uint256[] memory _dayPayoutTotal, uint256[] memory _dayDividends)
    {
        require(beginDay < endDay && endDay <= globals.dailyDataCount, "NUG: range invalid");

        _dayStakeSharesTotal = new uint256[](endDay - beginDay);
        _dayPayoutTotal = new uint256[](endDay - beginDay);
        _dayDividends = new uint256[](endDay - beginDay);

        uint256 src = beginDay;
        uint256 dst = 0;
        do {
            _dayStakeSharesTotal[dst] = uint256(dailyData[src].dayStakeSharesTotal);
            _dayPayoutTotal[dst++] = uint256(dailyData[src].dayPayoutTotal);
            _dayDividends[dst++] = dailyData[src].dayDividends;
        } while (++src < endDay);

        return (_dayStakeSharesTotal, _dayPayoutTotal, _dayDividends);
    }


/**
 * @dev Efficiently delete from an unordered array by moving the last element
 * to the "hole" and reducing the array length. Can change the order of the list
 * and invalidate previously held indexes.
 * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd()
 * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage
 * @param stakeIndex Index of the element to delete
 */

function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex)
        internal
    {
        uint256 lastIndex = stakeListRef.length - 1;

        /* Skip the copy if element to be removed is already the last element */
        if (stakeIndex != lastIndex) {
            /* Copy last element to the requested element's "hole" */
            stakeListRef[stakeIndex] = stakeListRef[lastIndex];
        }

        /*
            Reduce the array length now that the array is contiguous.
            Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex'
        */
        stakeListRef.pop();
    }


/**
 * @dev Estimate the stake payout for an incomplete day
 * @param g Cache of stored globals
 * @param stakeSharesParam Param from stake to calculate bonuses for
 * @param day Day to calculate bonuses for
 * @return Payout in Guns
 */

function _estimatePayoutRewardsDay(GlobalsCache memory g, uint256 stakeSharesParam, uint256 day)
        internal
        view
        returns (uint256 payout)
    {
        /* Prevent updating state for this estimation */
        GlobalsCache memory gTmp;
        _globalsCacheSnapshot(g, gTmp);

        DailyRoundState memory rs;
        rs._allocSupplyCached = totalSupply() + g._lockedGunsTotal;

        _dailyRoundCalc(gTmp, rs, day);

        /* Stake is no longer locked so it must be added to total as if it were */
        gTmp._stakeSharesTotal += stakeSharesParam;

        payout = rs._payoutTotal * stakeSharesParam / gTmp._stakeSharesTotal;

        return payout;
    }


/**
 * @dev PUBLIC FACING: Open a stake.
 * @param newStakedGuns Number of Guns to stake
 * @param newStakedDays Number of days to stake
 */

function stakeStart(uint256 newStakedGuns, uint256 newStakedDays)
        external
    {
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        /* Enforce the minimum stake time */
        require(newStakedDays >= MIN_STAKE_DAYS, "NUG: newStakedDays lower than minimum");

        /* Check if log data needs to be updated */
        _dailyDataUpdateAuto(g);

        _stakeStart(g, newStakedGuns, newStakedDays);

        /* Remove staked Guns from balance of staker */
        _burn(msg.sender, newStakedGuns);

        _globalsSync(g, gSnapshot);
    }


/**
 * @dev Calculates total stake payout including rewards for a multi-day range
 * @param g Cache of stored globals
 * @param stakeSharesParam Param from stake to calculate bonuses for
 * @param beginDay First day to calculate bonuses for
 * @param endDay Last day (non-inclusive) of range to calculate bonuses for
 * @return Payout in Guns
 */

function _calcPayoutRewards(
        GlobalsCache memory g,
        uint256 stakeSharesParam,
        uint256 beginDay,
        uint256 endDay
    )
        private
        view
        returns (uint256 payout)
    {
        uint256 counter;

        for (uint256 day = beginDay; day < endDay; day++) {
            uint256 dayPayout;

            dayPayout = dailyData[day].dayPayoutTotal * stakeSharesParam
                / dailyData[day].dayStakeSharesTotal;

            if (counter < 4) {
                counter++;
            }
            /* Eligible to receive bonus */
            else {
                dayPayout = (dailyData[day].dayPayoutTotal * stakeSharesParam
                / dailyData[day].dayStakeSharesTotal) * BONUS_DAY_SCALE;
                counter = 0;
            }

            payout += dayPayout;
        }

        return payout;
    }


/**
 * @dev Calculates user dividends
 * @param g Cache of stored globals
 * @param stakeSharesParam Param from stake to calculate bonuses for
 * @param beginDay First day to calculate bonuses for
 * @param endDay Last day (non-inclusive) of range to calculate bonuses for
 * @return Payout in Guns
 */

function _calcPayoutDividendsReward(
        GlobalsCache memory g,
        uint256 stakeSharesParam,
        uint256 beginDay,
        uint256 endDay
    )
        private
        view
        returns (uint256 payout)
    {

        for (uint256 day = beginDay; day < endDay; day++) {
            uint256 dayPayout;

            /* user's share of 85% of the day's dividends, 15% total will drip to NUI platform */
            dayPayout += ((dailyData[day].dayDividends * 85) / 100) * stakeSharesParam
            / dailyData[day].dayStakeSharesTotal;

            payout += dayPayout;
        }

        return payout;
    }


/**
 * @dev PUBLIC FACING: Enter the auction lobby for the current round
 * @param referrerAddr TRX address of referring user (optional; 0x0 for no referrer)
 */

function xfLobbyEnter(address referrerAddr)
        external
        payable
    {
        uint256 enterDay = _currentDay();

        uint256 rawAmount = msg.value;
        require(rawAmount != 0, "NUG: Amount required");

        XfLobbyQueueStore storage qRef = xfLobbyMembers[enterDay][msg.sender];

        uint256 entryIndex = qRef.tailIndex++;

        qRef.entries[entryIndex] = XfLobbyEntryStore(uint96(rawAmount), referrerAddr);

        xfLobby[enterDay] += rawAmount;

        emit XfLobbyEnter(
            block.timestamp,
            enterDay,
            entryIndex,
            rawAmount
        );
    }


/**
 * @dev PUBLIC FACING: Release 15% for NUI Drip from daily divs
 */

function xfFlush()
        external
    {
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);

        require(address(this).balance != 0, "NUG: No value");

        require(LAST_FLUSHED_DAY < _currentDay(), "NUG: Invalid day");

        _dailyDataUpdateAuto(g);

        NUI_SHARE_ADDR.transfer((dailyData[LAST_FLUSHED_DAY].dayDividends * 15) / 100);

        LAST_FLUSHED_DAY++;

        _globalsSync(g, gSnapshot);
    }


/**
 * @dev PUBLIC FACING: Return the lobby days that a user is in with a single call
 * @param memberAddr TRX address of the user
 * @return Bit vector of lobby day numbers
 */

function xfLobbyPendingDays(address memberAddr)
        external
        view
        returns (uint256[XF_LOBBY_DAY_WORDS] memory words)
    {
        uint256 day = _currentDay() + 1;

        while (day-- != 0) {
            if (xfLobbyMembers[day][memberAddr].tailIndex > xfLobbyMembers[day][memberAddr].headIndex) {
                words[day >> 8] |= 1 << (day & 255);
            }
        }

        return words;
    }


/**
 * @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
 * - Only callable by the LendingPool, as extra state updates there need to be managed
 * @param user The owner of the aTokens, getting them burned
 * @param receiverOfUnderlying The address that will receive the underlying
 * @param amount The amount being burned
 * @param index The new liquidity index of the reserve
 **/

function burn(
    address user,
    address receiverOfUnderlying,
    uint256 amount,
    uint256 index
  ) external override onlyLendingPool {
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT);

    ExtData memory e = _fetchExtData();
    _burnScaled(user, amountScaled, e);
    _totalGonsDeposited = _totalGonsDeposited.sub(
      _amplToGons(e.totalAMPLSupply, amountScaled).toInt256Safe()
    );

    IERC20(UNDERLYING_ASSET_ADDRESS).safeTransfer(receiverOfUnderlying, amount);

    emit Transfer(user, address(0), amount);
    emit Burn(user, receiverOfUnderlying, amount, index);
  }


/**
 * @dev Mints `amount` aTokens to `user`
 * - Only callable by the LendingPool, as extra state updates there need to be managed
 * @param user The address receiving the minted tokens
 * @param amount The amount of tokens getting minted
 * @param index The new liquidity index of the reserve
 * @return `true` if the the previous balance of the user was 0
 */

function mint(
    address user,
    uint256 amount,
    uint256 index
  ) external override onlyLendingPool returns (bool) {
    uint256 previousBalanceInternal = super.balanceOf(user);

    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);

    ExtData memory e = _fetchExtData();
    _mintScaled(user, amountScaled, e);
    _totalGonsDeposited = _totalGonsDeposited.add(
      _amplToGons(e.totalAMPLSupply, amountScaled).toInt256Safe()
    );

    emit Transfer(address(0), user, amount);
    emit Mint(user, amount, index);

    return previousBalanceInternal == 0;
  }


/**
 * @dev Mints aTokens to the reserve treasury
 * - Only callable by the LendingPool
 * @param amount The amount of tokens getting minted
 * @param index The new liquidity index of the reserve
 */

function mintToTreasury(uint256 amount, uint256 index) external override onlyLendingPool {
    if (amount == 0) {
      return;
    }

    // Compared to the normal mint, we don't check for rounding errors.
    // The amount to mint can easily be very small since it is a fraction of the interest accrued.
    // In that case, the treasury will experience a (very small) loss, but it
    // wont cause potentially valid transactions to fail.
    uint256 amountScaled = amount.rayDiv(index);
    // require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);

    ExtData memory e = _fetchExtData();
    _mintScaled(RESERVE_TREASURY_ADDRESS, amountScaled, e);
    _totalGonsDeposited = _totalGonsDeposited.add(
      _amplToGons(e.totalAMPLSupply, amountScaled).toInt256Safe()
    );

    emit Transfer(address(0), RESERVE_TREASURY_ADDRESS, amount);
    emit Mint(RESERVE_TREASURY_ADDRESS, amount, index);
  }


/**
 * @dev Transfers aTokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
 * - Only callable by the LendingPool
 * @param from The address getting liquidated, current owner of the aTokens
 * @param to The recipient
 * @param value The amount of tokens getting transferred
 **/

function transferOnLiquidation(
    address from,
    address to,
    uint256 value
  ) external override onlyLendingPool {
    // Being a normal transfer, the Transfer() and BalanceTransfer() are emitted
    // so no need to emit a specific event here
    _transfer(from, to, value, false);

    emit Transfer(from, to, value);
  }


/**
 * @dev implements the permit function as for
 * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md
 * @param owner The owner of the funds
 * @param spender The spender
 * @param value The amount
 * @param deadline The deadline timestamp, type(uint256).max for max deadline
 * @param v Signature param
 * @param s Signature param
 * @param r Signature param
 */

function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) external {
    require(owner != address(0), 'INVALID_OWNER');
    //solium-disable-next-line
    require(block.timestamp <= deadline, 'INVALID_EXPIRATION');
    uint256 currentValidNonce = _nonces[owner];
    bytes32 digest =
      keccak256(
        abi.encodePacked(
          '\x19\x01',
          DOMAIN_SEPARATOR,
          keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
        )
      );
    require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE');
    _nonces[owner] = currentValidNonce.add(1);
    _approve(owner, spender, value);
  }


/**
 * @dev Transfers the aTokens between two users. Validates the transfer
 * (ie checks for valid HF after the transfer) if required
 * @param from The source address
 * @param to The destination address
 * @param amount The amount getting transferred
 * @param validate `true` if the transfer needs to be validated
 **/

function _transfer(
    address from,
    address to,
    uint256 amount,
    bool validate
  ) internal {
    uint256 index = POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS);
    uint256 amountScaled = amount.rayDiv(index);

    ExtData memory e = _fetchExtData();
    uint256 totalSupplyInternal = super.totalSupply();

    uint256 scaledTotalSupply = _scaledTotalSupply(e, _totalGonsDeposited);
    uint256 fromBalanceScaled = _scaledBalanceOf(super.balanceOf(from), totalSupplyInternal, scaledTotalSupply);
    uint256 toBalanceScaled = _scaledBalanceOf(super.balanceOf(to), totalSupplyInternal, scaledTotalSupply);

    uint256 fromBalanceBefore = fromBalanceScaled.rayMul(index);
    uint256 toBalanceBefore = toBalanceScaled.rayMul(index);

    _transferScaled(from, to, amountScaled, e);

    if (validate) {
      POOL.finalizeTransfer(
        UNDERLYING_ASSET_ADDRESS,
        from,
        to,
        amount,
        fromBalanceBefore,
        toBalanceBefore
      );
    }

    emit BalanceTransfer(from, to, amount, index);
  }


/**
 * @dev calculates the total supply of the specific aToken
 * since the balance of every single user increases over time, the total supply
 * does that too.
 * @return the current total supply
 **/

function totalSupply() public view override(IncentivizedERC20, IERC20) returns (uint256) {
    uint256 currentSupplyScaled = _scaledTotalSupply(_fetchExtData(), _totalGonsDeposited);

    if (currentSupplyScaled == 0) {
      // currentSupplyInternal should also be zero in this case (super.totalSupply())
      return 0;
    }

    return currentSupplyScaled.rayMul(POOL.getReserveNormalizedIncome(UNDERLYING_ASSET_ADDRESS));
  }


/**
 * @dev transferAmountInternal = (transferAmountScaled * totalSupplyInternal) / scaledTotalSupply
 **/

function _transferScaled(address from, address to, uint256 transferAmountScaled, ExtData memory e) private returns (uint256) {
    uint256 totalSupplyInternal = super.totalSupply();
    uint256 scaledTotalSupply = _scaledTotalSupply(e, _totalGonsDeposited);
    uint256 transferAmountInternal = transferAmountScaled.mul(totalSupplyInternal).div(scaledTotalSupply);
    super._transfer(from, to, transferAmountInternal);
  }


/**
 * @dev mintAmountInternal is mint such that the following holds true
 *
 * (userBalanceInternalBefore+mintAmountInternal)/(totalSupplyInternalBefore+mintAmountInternal)
 *    = (userBalanceScaledBefore+mintAmountScaled)/(scaledTotalSupplyBefore+mintAmountScaled)
 *
 * scaledTotalSupplyAfter = scaledTotalSupplyBefore+mintAmountScaled
 * userBalanceScaledAfter = userBalanceScaledBefore+mintAmountScaled
 * otherBalanceScaledBefore = scaledTotalSupplyBefore-userBalanceScaledBefore
 *
 * mintAmountInternal = (totalSupplyInternalBefore*userBalanceScaledAfter - scaledTotalSupplyAfter*userBalanceInternalBefore)/otherBalanceScaledBefore
 **/

function _mintScaled(address user, uint256 mintAmountScaled, ExtData memory e) private {
    uint256 totalSupplyInternalBefore = super.totalSupply();
    uint256 userBalanceInternalBefore = super.balanceOf(user);

    // First mint
    if(totalSupplyInternalBefore == 0) {
      uint256 mintAmountInternal = _amplToGons(e.totalAMPLSupply, mintAmountScaled);
      _mint(user, mintAmountInternal);
      return;
    }

    uint256 scaledTotalSupplyBefore = _scaledTotalSupply(e, _totalGonsDeposited);

    uint256 userBalanceScaledBefore = _scaledBalanceOf(userBalanceInternalBefore, totalSupplyInternalBefore, scaledTotalSupplyBefore);
    uint256 otherBalanceScaledBefore = scaledTotalSupplyBefore.sub(userBalanceScaledBefore);

    uint256 scaledTotalSupplyAfter = scaledTotalSupplyBefore.add(mintAmountScaled);
    uint256 userBalanceScaledAfter = userBalanceScaledBefore.add(mintAmountScaled);
    uint256 mintAmountInternal = 0;

    // Lone user
    if(otherBalanceScaledBefore == 0) {
      uint256 mintAmountInternal = mintAmountScaled.mul(totalSupplyInternalBefore).div(scaledTotalSupplyBefore);
      _mint(user, mintAmountInternal);
      return;
    }

    mintAmountInternal = totalSupplyInternalBefore
      .mul(userBalanceScaledAfter)
      .sub(scaledTotalSupplyAfter.mul(userBalanceInternalBefore))
      .div(otherBalanceScaledBefore);

    _mint(user, mintAmountInternal);
  }


/**
 * @dev burnAmountInternal is burnt such that the following holds true
 *
 * (userBalanceInternalBefore-burnAmountInternal)/(totalSupplyInternalBefore-burnAmountInternal)
 *    = (userBalanceScaledBefore-burnAmountScaled)/(scaledTotalSupplyBefore-burnAmountScaled)
 *
 * scaledTotalSupplyAfter = scaledTotalSupplyBefore-burnAmountScaled
 * userBalanceScaledAfter = userBalanceScaledBefore-burnAmountScaled
 * otherBalanceScaledBefore = scaledTotalSupplyBefore-userBalanceScaledBefore
 *
 * burnAmountInternal = (scaledTotalSupplyAfter*userBalanceInternalBefore - totalSupplyInternalBefore*userBalanceScaledAfter)/otherBalanceScaledBefore
 **/

function _burnScaled(address user, uint256 burnAmountScaled, ExtData memory e) private {
    uint256 totalSupplyInternalBefore = super.totalSupply();
    uint256 userBalanceInternalBefore = super.balanceOf(user);

    uint256 scaledTotalSupplyBefore = _scaledTotalSupply(e, _totalGonsDeposited);
    uint256 userBalanceScaledBefore = _scaledBalanceOf(userBalanceInternalBefore, totalSupplyInternalBefore, scaledTotalSupplyBefore);
    uint256 otherBalanceScaledBefore = scaledTotalSupplyBefore.sub(userBalanceScaledBefore);

    uint256 scaledTotalSupplyAfter = scaledTotalSupplyBefore.sub(burnAmountScaled);
    uint256 userBalanceScaledAfter = userBalanceScaledBefore.sub(burnAmountScaled);
    uint256 burnAmountInternal = 0;

    // Lone user
    if(otherBalanceScaledBefore == 0) {
      uint256 burnAmountInternal = burnAmountScaled.mul(totalSupplyInternalBefore).div(scaledTotalSupplyBefore);
      _burn(user, burnAmountInternal);
      return;
    }

    burnAmountInternal = scaledTotalSupplyAfter
      .mul(userBalanceInternalBefore)
      .sub(totalSupplyInternalBefore.mul(userBalanceScaledAfter))
      .div(otherBalanceScaledBefore);
    _burn(user, burnAmountInternal);
  }


/// @dev Update crowdsale parameter

function updateParams(
uint256 _tokenExchangeRate,
uint256 _tokenCrowdsaleCap,
uint256 _fundingStartBlock,
uint256 _fundingEndBlock) external
{
assert(block.number < fundingStartBlock);
assert(!isFinalized);

// update system parameters
tokenExchangeRate = _tokenExchangeRate;
tokenCrowdsaleCap = _tokenCrowdsaleCap;
fundingStartBlock = _fundingStartBlock;
fundingEndBlock = _fundingEndBlock;
}


// ------------------------------------------------------------------------
// Transfer `tokens` from the `from` account to the `to` account
//
// The calling account must already have sufficient tokens approve(...)-d
// for spending from the `from` account and
// - From account must have sufficient balance to transfer
// - Spender must have sufficient allowance to transfer
// ------------------------------------------------------------------------

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        require(from != address(0));
        require(to != address(0));
        require(tokens > 0);
        require(balances[from] >= tokens);
        require(allowed[from][msg.sender] >= tokens);

        balances[from] = balances[from].sub(tokens);
        allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens);
        balances[to] = balances[to].add(tokens);
        emit Transfer(from, to, tokens);
        return true;
    }


/**
 * Add a new contract.
 * This function can only be called by the owner of the smart contract.
 *
 * @param _contractId 		Contract Id
 * @param _data		        Contract Data
 */

function addContract(uint _contractId, string memory _data) public checkSenderIsOwner
    returns(uint)
    {
        Contract storage newContract = contracts[_contractId];
        require(newContract.contractId == 0, "Contract already created.");

        newContract.contractId = _contractId;
        newContract.data = _data;

        // emitting the event that a new contract has been registered
        emit newContractRegistered(_contractId);

        return _contractId;
    }


/**
 * @dev Insert node `_node` with a value
 * @param self stored linked list from contract
 * @param _node new node to insert
 * @param _value value of the new `_node` to insert
 * @notice If the `_node` does not exists, it's added to the list
 *   if the `_node` already exists, it updates its value.
 */

function set(List storage self, uint256 _node, uint256 _value) internal {
        // Check if node previusly existed
        if (self.exists[_node]) {

            // Load the new and old position
            (uint256 leftOldPos, uint256 leftNewPos) = self.findOldAndNewLeftPosition(_node, _value);

            // If node position changed, we need to re-do the linking
            if (leftOldPos != leftNewPos && _node != leftNewPos) {
                // Remove prev link
                self.links[leftOldPos] = self.links[_node];

                // Create new link
                uint256 next = self.links[leftNewPos];
                self.links[leftNewPos] = _node;
                self.links[_node] = next;
            }
        } else {
            // Update size of the list
            self.size = self.size + 1;
            // Set node as existing
            self.exists[_node] = true;
            // Find position for the new node and update the links
            uint256 leftPosition = self.findLeftPosition(_value);
            uint256 next = self.links[leftPosition];
            self.links[leftPosition] = _node;
            self.links[_node] = next;
        }

        // Set the value for the node
        self.values[_node] = _value;
    }


/**
 * @dev Returns the previus node of a given `_node`
 *   alongside to the previus node of a hypothetical new `_value`
 * @param self stored linked list from contract
 * @param _node a node to search for its left node
 * @param _value a value to seach for its hypothetical left node
 * @return `leftNodePost` the node previus to the given `_node` and
 *   `leftValPost` the node previus to the hypothetical new `_value`
 * @notice This method performs two seemingly unrelated tasks at the same time
 *   because both of those tasks require a list iteration, thus saving gas.
 */

function findOldAndNewLeftPosition(
        List storage self,
        uint256 _node,
        uint256 _value
    ) internal view returns (
        uint256 leftNodePos,
        uint256 leftValPos
    ) {
        // Find old and new value positions
        bool foundNode;
        bool foundVal;

        // Iterate links
        uint256 c = HEAD;
        while (!foundNode || !foundVal) {
            uint256 next = self.links[c];

            // We should have found the old position
            // the new one must be at the end
            if (next == 0) {
                leftValPos = c;
                break;
            }

            // If the next node is the current node
            // we found the old position
            if (next == _node) {
                leftNodePos = c;
                foundNode = true;
            }

            // If the next value is higher and we didn't found one yet
            // the next value if the position
            if (self.values[next] > _value && !foundVal) {
                leftValPos = c;
                foundVal = true;
            }

            c = next;
        }
    }


/**
 * @dev Get the left node for a given hypothetical `_value`
 * @param self stored linked list from contract
 * @param _value value to seek
 * @return uint256 left node for the given value
 */

function findLeftPosition(List storage self, uint256 _value) internal view returns (uint256) {
        uint256 next = HEAD;
        uint256 c;

        do {
            c = next;
            next = self.links[c];
        } while(self.values[next] < _value && next != 0);

        return c;
    }


/**
 * @dev Get the node on a given `_position`
 * @param self stored linked list from contract
 * @param _position node position to retrieve
 * @return the node key
 */

function nodeAt(List storage self, uint256 _position) internal view returns (uint256) {
        uint256 next = self.links[HEAD];
        for (uint256 i = 0; i < _position; i++) {
            next = self.links[next];
        }

        return next;
    }


/**
 * @dev Removes an entry from the sorted list
 * @param self stored linked list from contract
 * @param _node node to remove from the list
 */

function remove(List storage self, uint256 _node) internal {
        require(self.exists[_node], "the node does not exists");

        uint256 c = self.links[HEAD];
        while (c != 0) {
            uint256 next = self.links[c];
            if (next == _node) {
                break;
            }

            c = next;
        }

        self.size -= 1;
        self.exists[_node] = false;
        self.links[c] = self.links[_node];
        delete self.links[_node];
        delete self.values[_node];
    }


/**
 * @dev Get median beetween entry from the sorted list
 * @param self stored linked list from contract
 * @return uint256 the median
 */

function median(List storage self) internal view returns (uint256) {
        uint256 elements = self.size;
        if (elements % 2 == 0) {
            uint256 node = self.nodeAt(elements / 2 - 1);
            return Math.average(self.values[node], self.values[self.links[node]]);
        } else {
            return self.values[self.nodeAt(elements / 2)];
        }
    }


/**
 * @dev Adds a `_signer` who is going to be able to provide a new rate
 * @param _signer Address of the signer
 * @param _name Metadata - Human readable name of the signer
 */

function addSigner(address _signer, string calldata _name) external onlyOwner {
        require(!isSigner[_signer], "signer already defined");
        require(signerWithName[_name] == address(0), "name already in use");
        require(bytes(_name).length > 0, "name can't be empty");
        isSigner[_signer] = true;
        signerWithName[_name] = _signer;
        nameOfSigner[_signer] = _name;
    }


/**
 * @dev Updates the `_name` metadata of a given `_signer`
 * @param _signer Address of the signer
 * @param _name Metadata - Human readable name of the signer
 */

function setName(address _signer, string calldata _name) external onlyOwner {
        require(isSigner[_signer], "signer not defined");
        require(signerWithName[_name] == address(0), "name already in use");
        require(bytes(_name).length > 0, "name can't be empty");
        string memory oldName = nameOfSigner[_signer];
        signerWithName[oldName] = address(0);
        signerWithName[_name] = _signer;
        nameOfSigner[_signer] = _name;
    }


/**
 * @dev Removes an existing `_signer`, removing any provided rate
 * @param _signer Address of the signer
 */

function removeSigner(address _signer) external onlyOwner {
        require(isSigner[_signer], "address is not a signer");
        string memory signerName = nameOfSigner[_signer];

        isSigner[_signer] = false;
        signerWithName[signerName] = address(0);
        nameOfSigner[_signer] = "";

        // Only remove from list if it provided a value
        if (list.exists[uint256(_signer)]) {
            list.remove(uint256(_signer));
        }
    }


/**
 * @dev Reads the rate provided by the Oracle
 *   this being the median of the last rate provided by each signer
 * @param _oracleData Oracle auxiliar data defined in the RCN Oracle spec
 *   not used for this oracle, but forwarded in case of upgrade.
 * @return `_equivalent` is the median of the values provided by the signer
 *   `_tokens` are equivalent to `_equivalent` in the currency of the Oracle
 */

function readSample(bytes memory _oracleData) public view returns (uint256 _tokens, uint256 _equivalent) {
        // Check if paused
        require(!paused && !pausedProvider.isPaused(), "contract paused");

        // Check if Oracle contract has been upgraded
        RateOracle _upgrade = upgrade;
        if (address(_upgrade) != address(0)) {
            return _upgrade.readSample(_oracleData);
        }

        // Tokens is always base
        _tokens = BASE;
        _equivalent = list.median();
    }


/**
 * @dev Removes a value from a set. O(1).
 *
 * Returns true if the value was removed from the set, that is if it was
 * present.
 */

function _remove(Set storage set, bytes32 value) private returns (bool) {
            // We read and store the value's index to prevent multiple reads from the same storage slot
            uint256 valueIndex = set._indexes[value];

            if (valueIndex != 0) { // Equivalent to contains(set, value)

                uint256 toDeleteIndex = valueIndex - 1;
                uint256 lastIndex = set._values.length - 1;

                bytes32 lastvalue = set._values[lastIndex];

                set._values[toDeleteIndex] = lastvalue;
                set._indexes[lastvalue] = toDeleteIndex + 1; // All indexes are 1-based

                set._values.pop();

                delete set._indexes[value];

                return true;
            } else {
                return false;
            }
        }


/**
 * @dev Remove time lock, only locker can remove
 * @param account The address want to know the time lock state.
 * @param index Time lock index
 */

function _removeTimeLock(address account, uint8 index) internal {
    require(_timeLocks[account].length > index && index >= 0, "Time Lock: index must be valid");

    uint len = _timeLocks[account].length;
    if (len - 1 != index) { // if it is not last item, swap it
      _timeLocks[account][index] = _timeLocks[account][len - 1];
    }
    _timeLocks[account].pop();
  }


/**
 * @dev lock and pause and check time lock before transfer token
 */

function _beforeTokenTransfer(address from, address to, uint256 amount) internal view {
    require(!isLocked(from), "Lockable: token transfer from locked account");
    require(!isLocked(to), "Lockable: token transfer to locked account");
    require(!paused(), "Pausable: token transfer while paused");
    require(balanceOf(from).sub(getTimeLockedAmount(from)) >= amount, "Lockable: token transfer from time locked account");
  }


/**
 * @dev Function to delete lock
 * @param granted The address that was locked
 * @param index The index of lock
 */

function deleteLock(address granted, uint8 index) public whenNotPaused onlyOwnerOrOperator {
    require(grantedLocks[granted].length > index);

    uint len = grantedLocks[granted].length;
    if (len == 1) {
      delete grantedLocks[granted];
    } else {
      if (len - 1 != index) {
        grantedLocks[granted][index] = grantedLocks[granted][len - 1];
      }
      delete grantedLocks[granted][len - 1];
    }
  }


/**
 * @dev get locked amount of address
 * @param granted The address want to know the lock state.
 * @return locked amount
 */

function getLockedAmount(address granted) public view returns(uint) {
    uint lockedAmount = 0;

    uint len = grantedLocks[granted].length;
    for (uint i = 0; i < len; i++) {
      if (now < grantedLocks[granted][i].expiresAt) {
        lockedAmount = lockedAmount.add(grantedLocks[granted][i].amount);
      }
    }
    return lockedAmount;
  }


/**
 * @dev Creates a new Oracle contract for a given `_symbol`
 * @param _symbol metadata symbol for the currency of the oracle to create
 * @param _name metadata name for the currency of the oracle
 * @param _decimals metadata number of decimals to express the common denomination of the currency
 * @param _token metadata token address of the currency
 *   (if the currency has no token, it should be the address 0)
 * @param _maintainer metadata maintener human readable name
 * @notice Only one oracle by symbol can be created
 */

function newOracle(
        string calldata _symbol,
        string calldata _name,
        uint256 _decimals,
        address _token,
        string calldata _maintainer
    ) external onlyOwner {
        // Check for duplicated oracles
        require(symbolToOracle[_symbol] == address(0), "Oracle already exists");
        // Create oracle contract
        MultiSourceOracle oracle = new MultiSourceOracle(
            _symbol,
            _name,
            _decimals,
            _token,
            _maintainer
        );
        // Sanity check new oracle
        assert(bytes(oracleToSymbol[address(oracle)]).length == 0);
        // Save Oracle in registry
        symbolToOracle[_symbol] = address(oracle);
        oracleToSymbol[address(oracle)] = _symbol;
        // Emit events
        emit NewOracle(
            _symbol,
            address(oracle),
            _name,
            _decimals,
            _token,
            _maintainer
        );
    }


/**
 * @dev Adds a `_signer` to multiple `_oracles`
 * @param _oracles List of oracles on which add the `_signer`
 * @param _signer Address of the signer to be added
 * @param _name Human readable metadata name of the `_signer`
 * @notice Acts as a proxy for all the `_oracles` `_oracle.addSigner`
 */

function addSignerToOracles(
        address[] calldata _oracles,
        address _signer,
        string calldata _name
    ) external onlyOwner {
        for (uint256 i = 0; i < _oracles.length; i++) {
            address oracle = _oracles[i];
            MultiSourceOracle(oracle).addSigner(_signer, _name);
            emit AddSigner(oracle, _signer, _name);
        }
    }


/**
 * @dev Removes a `_signer` from multiple `_oracles`
 * @param _oracles List of oracles on which remove the `_signer`
 * @param _signer Address of the signer to be removed
 * @notice Acts as a proxy for all the `_oracles` `_oracle.removeSigner`
 */

function removeSignerFromOracles(
        address[] calldata _oracles,
        address _signer
    ) external onlyOwner {
        for (uint256 i = 0; i < _oracles.length; i++) {
            address oracle = _oracles[i];
            MultiSourceOracle(oracle).removeSigner(_signer);
            emit RemoveSigner(oracle, _signer);
        }
    }


/**
 * @dev Provides multiple rates for a set of oracles, with the same signer
 *   msg.sender becomes the signer for all the provides
 *
 * @param _oracles List of oracles to provide a rate for
 * @param _rates List of rates to provide
 * @notice Acts as a proxy for multiples `_oracle.provide`, using the parameter `msg.sender` as signer
 */

function provideMultiple(
        address[] calldata _oracles,
        uint256[] calldata _rates
    ) external {
        uint256 length = _oracles.length;
        require(length == _rates.length, "arrays should have the same size");

        for (uint256 i = 0; i < length; i++) {
            address oracle = _oracles[i];
            uint256 rate = _rates[i];
            MultiSourceOracle(oracle).provide(msg.sender, rate);
            emit Provide(oracle, msg.sender, rate);
        }
    }


// source: https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol

function toUint32(bytes memory _bytes, uint256 _start) private pure returns (uint32) {
        require(_bytes.length >= _start + 4, "OVM_MSG_WPR: out of bounds");
        uint32 tempUint;

        assembly {
            tempUint := mload(add(add(_bytes, 0x4), _start))
        }

        return tempUint;
    }


//1m pieces total
//reward begins at 4 and is cut in half every reward era (as tokens are mined)

function getMiningReward() public constant returns (uint) {
        //once we get half way thru the coins, only get 2 per block

         //every reward era, the reward amount halves.

         return (4 * 10**uint(decimals) ).div( 2**rewardEra ) ;

    }


/**
 * @notice read the current configuration of the coordinator.
 */

function getConfig()
    external
    view
    returns (
      uint16 minimumRequestConfirmations,
      uint32 fulfillmentFlatFeeLinkPPM,
      uint32 maxGasLimit,
      uint32 stalenessSeconds,
      uint32 gasAfterPaymentCalculation,
      uint96 minimumSubscriptionBalance,
      int256 fallbackWeiPerUnitLink
    )
  {
    Config memory config = s_config;
    return (
      config.minimumRequestConfirmations,
      config.fulfillmentFlatFeeLinkPPM,
      config.maxGasLimit,
      config.stalenessSeconds,
      config.gasAfterPaymentCalculation,
      config.minimumSubscriptionBalance,
      s_fallbackWeiPerUnitLink
    );
  }


/**
 * @dev calls target address with exactly gasAmount gas and data as calldata
 * or reverts if at least gasAmount gas is not available.
 * The maximum amount of gasAmount is all gas available but 1/64th.
 * The minimum amount of gasAmount is MIN_GAS_LIMIT.
 */

function callWithExactGas(
    uint256 gasAmount,
    address target,
    bytes memory data
  ) private returns (bool success) {
    // solhint-disable-next-line no-inline-assembly
    assembly {
      let g := gas()
      // Compute g -= MIN_GAS_LIMIT and check for underflow
      if lt(g, MIN_GAS_LIMIT) {
        revert(0, 0)
      }
      g := sub(g, MIN_GAS_LIMIT)
      // if g - g//64 <= gasAmount, revert
      // (we subtract g//64 because of EIP-150)
      if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
        revert(0, 0)
      }
      // solidity calls check that a contract actually exists at the destination, so we do the same
      if iszero(extcodesize(target)) {
        revert(0, 0)
      }
      // call and return whether we succeeded. ignore return data
      success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
    }
    return success;
  }


/**
 * @dev Returns an Ethereum Signed Message, created from a `hash`. This
 * produces hash corresponding to the one signed with the
 * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
 * JSON-RPC method as part of EIP-191.
 *
 * See {recover}.
 */

function toEthSignedMessageHash(bytes32 hash)
        internal
        pure
        returns (bytes32)
    {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return
            keccak256(
                abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
            );
    }


// Get the amount of gas used for fulfillment

function calculatePaymentAmount(
    uint256 startGas,
    uint256 gasAfterPaymentCalculation,
    uint32 fulfillmentFlatFeeLinkPPM,
    uint256 weiPerUnitGas
  ) internal view returns (uint96) {
    int256 weiPerUnitLink;
    weiPerUnitLink = getFeedData();
    if (weiPerUnitLink <= 0) {
      revert InvalidLinkWeiPrice(weiPerUnitLink);
    }
    // (1e18 juels/link) (wei/gas * gas) / (wei/link) = juels
    uint256 paymentNoFee = (1e18 * weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft())) /
      uint256(weiPerUnitLink);
    uint256 fee = 1e12 * uint256(fulfillmentFlatFeeLinkPPM);
    if (paymentNoFee > (1e27 - fee)) {
      revert PaymentTooLarge(); // Payment + fee cannot be more than all of the link in existence.
    }
    return uint96(paymentNoFee + fee);
  }


// Keep this separate from zeroing, perhaps there is a use case where consumers
// want to keep the subId, but withdraw all the link.

function cancelSubscription(uint64 subId, address to) external onlySubOwner(subId) nonReentrant {
    Subscription memory sub = s_subscriptions[subId];
    uint96 balance = sub.balance;
    // Note bounded by MAX_CONSUMERS;
    // If no consumers, does nothing.
    for (uint256 i = 0; i < sub.consumers.length; i++) {
      delete s_consumers[sub.consumers[i]][subId];
    }
    delete s_subscriptions[subId];
    s_totalBalance -= balance;
    if (!LINK.transfer(to, uint256(balance))) {
      revert InsufficientBalance();
    }
    emit SubscriptionCanceled(subId, to, balance);
  }


/**
 * @dev Setter round time.
 * @param _currentDeadlineInHours this value current deadline in hours.
 * @param _lastDeadlineInHours this value last deadline in hours.
 */

function _setRoundTime(uint _currentDeadlineInHours, uint _lastDeadlineInHours) internal {
        defaultCurrentDeadlineInHours = _currentDeadlineInHours;
        defaultLastDeadlineInHours = _lastDeadlineInHours;
        currentDeadline = block.timestamp + 60 * 60 * _currentDeadlineInHours;
        lastDeadline = block.timestamp + 60 * 60 * _lastDeadlineInHours;
    }


/**
 * @dev Setting info about participant from Bears or Bulls contract
 * @param _lastHero Address of participant
 * @param _deposit Amount of deposit
 */

function setInfo(address _lastHero, uint256 _deposit) public {
        require(address(BearsContract) == msg.sender || address(BullsContract) == msg.sender);

        if (address(BearsContract) == msg.sender) {
            require(depositBulls[currentRound][_lastHero] == 0, "You are already in bulls team");
            if (depositBears[currentRound][_lastHero] == 0)
                countOfBears++;
            totalSupplyOfBears = totalSupplyOfBears.add(_deposit.mul(90).div(100));
            depositBears[currentRound][_lastHero] = depositBears[currentRound][_lastHero].add(_deposit.mul(90).div(100));
        }

        if (address(BullsContract) == msg.sender) {
            require(depositBears[currentRound][_lastHero] == 0, "You are already in bears team");
            if (depositBulls[currentRound][_lastHero] == 0)
                countOfBulls++;
            totalSupplyOfBulls = totalSupplyOfBulls.add(_deposit.mul(90).div(100));
            depositBulls[currentRound][_lastHero] = depositBulls[currentRound][_lastHero].add(_deposit.mul(90).div(100));
        }

        lastHero = _lastHero;

        if (currentDeadline.add(120) <= lastDeadline) {
            currentDeadline = currentDeadline.add(120);
        } else {
            currentDeadline = lastDeadline;
        }

        jackPot += _deposit.mul(10).div(100);

        calculateProbability();
    }


/**
 * @dev Calculation probability for team's win
 */

function calculateProbability() public {
        require(winner == 0 && getState());

        totalGWSupplyOfBulls = GameWaveContract.balanceOf(address(BullsContract));
        totalGWSupplyOfBears = GameWaveContract.balanceOf(address(BearsContract));
        uint256 percent = (totalSupplyOfBulls.add(totalSupplyOfBears)).div(100);

        if (totalGWSupplyOfBulls < 1 ether) {
            totalGWSupplyOfBulls = 0;
        }

        if (totalGWSupplyOfBears < 1 ether) {
            totalGWSupplyOfBears = 0;
        }

        if (totalGWSupplyOfBulls <= totalGWSupplyOfBears) {
            uint256 difference = totalGWSupplyOfBears.sub(totalGWSupplyOfBulls).div(0.01 ether);

            probabilityOfBears = totalSupplyOfBears.mul(100).div(percent).add(estimateTokenPercent(difference));

            if (probabilityOfBears > 8000) {
                probabilityOfBears = 8000;
            }
            if (probabilityOfBears < 2000) {
                probabilityOfBears = 2000;
            }
            probabilityOfBulls = 10000 - probabilityOfBears;
        } else {
            uint256 difference = totalGWSupplyOfBulls.sub(totalGWSupplyOfBears).div(0.01 ether);
            probabilityOfBulls = totalSupplyOfBulls.mul(100).div(percent).add(estimateTokenPercent(difference));

            if (probabilityOfBulls > 8000) {
                probabilityOfBulls = 8000;
            }
            if (probabilityOfBulls < 2000) {
                probabilityOfBulls = 2000;
            }
            probabilityOfBears = 10000 - probabilityOfBulls;
        }

        totalGWSupplyOfBulls = GameWaveContract.balanceOf(address(BullsContract));
        totalGWSupplyOfBears = GameWaveContract.balanceOf(address(BearsContract));
    }