// Get the borrower's pending accumulated ETH reward, earned by their stake

function getPendingETHReward(address _borrower) public view override returns (uint) {
        uint snapshotETH = rewardSnapshots[_borrower].ETH;
        uint rewardPerUnitStaked = L_ETH.sub(snapshotETH);

        if ( rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) { return 0; }

        uint stake = Troves[_borrower].stake;

        uint pendingETHReward = stake.mul(rewardPerUnitStaked).div(DECIMAL_PRECISION);

        return pendingETHReward;
    }


// Get the borrower's pending accumulated LUSD reward, earned by their stake

function getPendingLUSDDebtReward(address _borrower) public view override returns (uint) {
        uint snapshotLUSDDebt = rewardSnapshots[_borrower].LUSDDebt;
        uint rewardPerUnitStaked = L_LUSDDebt.sub(snapshotLUSDDebt);

        if ( rewardPerUnitStaked == 0 || Troves[_borrower].status != Status.active) { return 0; }

        uint stake =  Troves[_borrower].stake;

        uint pendingLUSDDebtReward = stake.mul(rewardPerUnitStaked).div(DECIMAL_PRECISION);

        return pendingLUSDDebtReward;
    }


// Return the Troves entire debt and coll, including pending rewards from redistributions.

function getEntireDebtAndColl(
        address _borrower
    )
        public
        view
        override
        returns (uint debt, uint coll, uint pendingLUSDDebtReward, uint pendingETHReward)
    {
        debt = Troves[_borrower].debt;
        coll = Troves[_borrower].coll;

        pendingLUSDDebtReward = getPendingLUSDDebtReward(_borrower);
        pendingETHReward = getPendingETHReward(_borrower);

        debt = debt.add(pendingLUSDDebtReward);
        coll = coll.add(pendingETHReward);
    }


// Calculate a new stake based on the snapshots of the totalStakes and totalCollateral taken at the last liquidation

function _computeNewStake(uint _coll) internal view returns (uint) {
        uint stake;
        if (totalCollateralSnapshot == 0) {
            stake = _coll;
        } else {
            /*
            * The following assert() holds true because:
            * - The system always contains >= 1 trove
            * - When we close or liquidate a trove, we redistribute the pending rewards, so if all troves were closed/liquidated,
            * rewards would’ve been emptied and totalCollateralSnapshot would be zero too.
            */
            assert(totalStakesSnapshot > 0);
            stake = _coll.mul(totalStakesSnapshot).div(totalCollateralSnapshot);
        }
        return stake;
    }


/*
 * Remove a Trove owner from the TroveOwners array, not preserving array order. Removing owner 'B' does the following:
 * [A B C D E] => [A E C D], and updates E's Trove struct to point to its new array index.
 */

function _removeTroveOwner(address _borrower, uint TroveOwnersArrayLength) internal {
        Status troveStatus = Troves[_borrower].status;
        // It’s set in caller function `_closeTrove`
        assert(troveStatus != Status.nonExistent && troveStatus != Status.active);

        uint128 index = Troves[_borrower].arrayIndex;
        uint length = TroveOwnersArrayLength;
        uint idxLast = length.sub(1);

        assert(index <= idxLast);

        address addressToMove = TroveOwners[idxLast];

        TroveOwners[index] = addressToMove;
        Troves[addressToMove].arrayIndex = index;
        emit TroveIndexUpdated(addressToMove, index);

        TroveOwners.pop();
    }


/*
 * This function has two impacts on the baseRate state variable:
 * 1) decays the baseRate based on time passed since last redemption or LUSD borrowing operation.
 * then,
 * 2) increases the baseRate based on the amount redeemed, as a proportion of total supply
 */

function _updateBaseRateFromRedemption(uint _ETHDrawn,  uint _price, uint _totalLUSDSupply) internal returns (uint) {
        uint decayedBaseRate = _calcDecayedBaseRate();

        /* Convert the drawn ETH back to LUSD at face value rate (1 LUSD:1 USD), in order to get
        * the fraction of total supply that was redeemed at face value. */
        uint redeemedLUSDFraction = _ETHDrawn.mul(_price).div(_totalLUSDSupply);

        uint newBaseRate = decayedBaseRate.add(redeemedLUSDFraction.div(BETA));
        newBaseRate = LiquityMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%
        //assert(newBaseRate <= DECIMAL_PRECISION); // This is already enforced in the line above
        assert(newBaseRate > 0); // Base rate is always non-zero after redemption

        // Update the baseRate state variable
        baseRate = newBaseRate;
        emit BaseRateUpdated(newBaseRate);

        _updateLastFeeOpTime();

        return newBaseRate;
    }


/**
 @notice find the integer part of log2(p/q)
 => find largest x s.t p >= q * 2^x
 => find largest x s.t 2^x <= p / q
 */

function log2Int(uint256 _p, uint256 _q) internal pure returns (uint256) {
        uint256 res = 0;
        uint256 remain = _p / _q;
        while (remain > 0) {
            res++;
            remain /= 2;
        }
        return res - 1;
    }


/**
 @notice log2 for a number that it in [1,2)
 @dev _x is FP, return a FP
 @dev function is from Kyber. Long modified the condition to be (_x >= one) && (_x < two)
 to avoid the case where x = 2 may lead to incorrect result
 */

function log2ForSmallNumber(uint256 _x) internal pure returns (uint256) {
        uint256 res = 0;
        uint256 one = (uint256(1) << PRECISION_BITS);
        uint256 two = 2 * one;
        uint256 addition = one;

        require((_x >= one) && (_x < two), "MATH_ERROR");
        require(PRECISION_BITS < 125, "MATH_ERROR");

        for (uint256 i = PRECISION_BITS; i > 0; i--) {
            _x = (_x * _x) / one;
            addition = addition / 2;
            if (_x >= two) {
                _x = _x / 2;
                res += addition;
            }
        }

        return res;
    }


/**
 @notice log2 of (p/q). returns result in FP form
 @dev function is from Kyber.
 @dev _p & _q is FP, return a FP
 */

function logBase2(uint256 _p, uint256 _q) internal pure returns (uint256) {
        uint256 n = 0;

        if (_p > _q) {
            n = log2Int(_p, _q);
        }

        require(n * RONE <= BIG_NUMBER, "MATH_ERROR");
        require(!checkMultOverflow(_p, RONE), "MATH_ERROR");
        require(!checkMultOverflow(n, RONE), "MATH_ERROR");
        require(!checkMultOverflow(uint256(1) << n, _q), "MATH_ERROR");

        uint256 y = (_p * RONE) / (_q * (uint256(1) << n));
        uint256 log2Small = log2ForSmallNumber(y);

        assert(log2Small <= BIG_NUMBER);

        return n * RONE + log2Small;
    }


/**
 @notice calculate ln(p/q). returned result >= 0
 @dev function is from Kyber.
 @dev _p & _q is FP, return a FP
 */

function ln(uint256 p, uint256 q) internal pure returns (uint256) {
        uint256 ln2Numerator = 6931471805599453094172;
        uint256 ln2Denomerator = 10000000000000000000000;

        uint256 log2x = logBase2(p, q);

        require(!checkMultOverflow(ln2Numerator, log2x), "MATH_ERROR");

        return (ln2Numerator * log2x) / ln2Denomerator;
    }


/**
 @notice return e^exp in FP form
 @dev estimation by formula at http://pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/exp.html
 the function is based on exp function of:
 https://github.com/NovakDistributed/macroverse/blob/master/contracts/RealMath.sol
 @dev the function is expected to converge quite fast, after about 20 iteration
 @dev exp is a FP, return a FP
 */

function rpowe(uint256 exp) internal pure returns (uint256) {
        uint256 res = 0;

        uint256 curTerm = RONE;

        for (uint256 n = 0; ; n++) {
            res += curTerm;
            curTerm = rmul(curTerm, rdiv(exp, toFP(n + 1)));
            if (curTerm == 0) {
                break;
            }
            if (n == 500) {
                /*
                testing shows that in the most extreme case, it will take 430 turns to converge.
                however, it's expected that the numbers will not exceed 2^120 in normal situation
                the most extreme case is rpow((1<<256)-1,(1<<40)-1) (equal to rpow((2^256-1)/2^40,0.99..9))
                */
                revert("RPOWE_SLOW_CONVERGE");
            }
        }

        return res;
    }


/**
 @notice return base^exp with base in FP form and exp in Int
 @dev this function use a technique called: exponentiating by squaring
 complexity O(log(q))
 @dev function is from Kyber.
 @dev base is a FP, exp is an Int, return a FP
 */

function rpowi(uint256 base, uint256 exp) internal pure returns (uint256) {
        uint256 res = exp % 2 != 0 ? base : RONE;

        for (exp /= 2; exp != 0; exp /= 2) {
            base = rmul(base, base);

            if (exp % 2 != 0) {
                res = rmul(res, base);
            }
        }
        return res;
    }


/**
 @dev y is an Int, returns an Int
 @dev babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
 @dev from Uniswap
 */

function sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }


/**
 * @dev update the balance of a type provided in _binBalances
 * @param _binBalances Uint256 containing the balances of objects
 * @param _index Index of the object in the provided bin
 * @param _amount Value to update the type balance
 * @param _operation Which operation to conduct :
 *     Operations.REPLACE : Replace type balance with _amount
 *     Operations.ADD     : ADD _amount to type balance
 *     Operations.SUB     : Substract _amount from type balance
 */

function updateTokenBalance(
    uint256 _binBalances,
    uint256 _index,
    uint256 _amount,
    Operations _operation) internal pure returns (uint256 newBinBalance)
  {
    uint256 objectBalance = 0;
    if (_operation == Operations.ADD) {
        objectBalance = getValueInBin(_binBalances, _index);
        newBinBalance = writeValueInBin(_binBalances, _index, objectBalance.add(_amount));
    } else if (_operation == Operations.SUB) {
        objectBalance = getValueInBin(_binBalances, _index);
        newBinBalance = writeValueInBin(_binBalances, _index, objectBalance.sub(_amount));
    } else if (_operation == Operations.REPLACE) {
        newBinBalance = writeValueInBin(_binBalances, _index, _amount);
    } else {
      revert("Invalid operation"); // Bad operation
    }

    return newBinBalance;
  }


/*
 * @dev return value in _binValue at position _index
 * @param _binValue uint256 containing the balances of TYPES_PER_UINT256 types
 * @param _index index at which to retrieve value
 * @return Value at given _index in _bin
 */

function getValueInBin(uint256 _binValue, uint256 _index) internal pure returns (uint256) {

    // Mask to retrieve data for a given binData
    uint256 mask = (uint256(1) << TYPES_BITS_SIZE) - 1;

    // Shift amount
    uint256 rightShift = 256 - TYPES_BITS_SIZE * (_index + 1);
    return (_binValue >> rightShift) & mask;
  }


// Make public for testing

function recover(address token, uint256[] memory auctionData, uint256[] memory ids, uint256[] memory amounts, bytes memory signature) internal view returns (address) {
        return SigUtil.recover(
            // This recreates the message that was signed on the client.
            keccak256(abi.encodePacked("\x19\x01", domainSeparator(), hashAuction(token, auctionData, ids, amounts))),
            signature
        );
    }


// function sendERC777Tokens(address _from, address _to, ERC777 _tokenContract, uint256 _amount, bytes calldata _data) external {
//     require(msg.sender == address(this), "can only be called by own");
//     _tokenContract.operatorSend(_from, _to, _amount, _data);
// }
// TODO safe /unsafe version
// function sendERC1155Tokens(address _from, address _to, ERC1155 _tokenContract, uint256 _tokenType, uint256 _amount, bytes calldata _data) external {
//     require(msg.sender == address(this), "can only be called by own");
//     _tokenContract.transfer...(_from, _to, _amount, _data);
// }
// TODO safe /unsafe version
// function sendERC721Tokens(address _from, address _to, ERC721 _tokenContract, uint256 _tokenType, bytes calldata _data) external {
//     require(msg.sender == address(this), "can only be called by own");
//     _tokenContract.transferFrom(_from, _to, _amount, _data);
// }

function executeERC20MetaTx(
        address _from,
        address _to,
        address _gasToken,
        bytes calldata _data,
        uint256[4] calldata params, // _nonce, _gasPrice, _txGas, _tokenGasPrice
        address _relayer,
        bytes calldata _sig,
        address _tokenReceiver,
        bool signedOnBehalf
    ) external returns (bool, bytes memory) {
        require(msg.sender != address(this), "can only be called externaly");
        uint256 initialGas = gasleft();
        ensureParametersValidity(_from, _gasToken, params, _relayer);
        ensureCorrectSigner(_from, _to, _gasToken, _data, params, _relayer, _sig, ERC20METATRANSACTION_TYPEHASH, signedOnBehalf);
        return performERC20MetaTx(_from, _to, _gasToken, _data, params, initialGas, _tokenReceiver);
    }


//override to block from emitting Transfer when not mErc20compatible

function _send(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData,
        bool _preventLocking
    )
        internal
    {
        callSender(_operator, _from, _to, _amount, _data, _operatorData);

        require(_to != address(0), "Cannot send to 0x0");
        require(mBalances[_from] >= _amount, "Not enough funds");

        mBalances[_from] = mBalances[_from] -= _amount;
        mBalances[_to] = mBalances[_to] += _amount;

        callRecipient(_operator, _from, _to, _amount, _data, _operatorData, _preventLocking);

        emit Sent(_operator, _from, _to, _amount, _data, _operatorData);
    }


/**
 * @dev See {IBEP1155MetadataURI-uri}.
 *
 * This implementation returns the same URI for *all* token types. It relies
 * on the token type ID substitution mechanism
 * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
 *
 * Clients calling this function must replace the `\{id\}` substring with the
 * actual token type ID.
 */

function uri(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory ipfsmetadata)
    {
        // return _uri;
        Metadata memory date = token_id[tokenId];
        ipfsmetadata = date.ipfsmetadata;
        // string memory ipfsmetadata = getmetadata(tokenId);

        return ipfsmetadata;
    }


/**
 * @dev See {IBEP1155-safeTransferFrom}.
 */

function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "BEP1155: caller is not owner nor approved"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }


/**
 * @dev See {IBEP1155-safeBatchTransferFrom}.
 */

function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, address(this)),
            "BEP1155: transfer caller is not owner nor approved"
        );
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }


/**
 * @dev xref:ROOT:BEP1155.adoc#batch-operations[Batched] version of {_burn}.
 *
 * Requirements:
 *
 * - `ids` and `amounts` must have the same length.
 */

function _burnBatch(
        address account,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        require(account != address(0), "BEP1155: burn from the zero address");
        require(
            ids.length == amounts.length,
            "BEP1155: ids and amounts length mismatch"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, account, address(0), ids, amounts, "");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 accountBalance = _balances[id][account];
            require(
                accountBalance >= amount,
                "BEP1155: burn amount exceeds balance"
            );
            unchecked {
                _balances[id][account] = accountBalance - amount;
            }
        }

        emit TransferBatch(operator, account, address(0), ids, amounts);
    }


// BNB TRANSFER PURCHASE

function saleToken(
        address payable from,
        uint256 tokenId,
        uint256 amount,
        uint256 nooftoken
    ) public payable {
        require(
            amount == order_place[from][tokenId].price.mul(nooftoken),
            "Invalid Balance"
        );
        _saleToken(from, tokenId, amount, "BNB");
        _setApprovalForAll(from, msg.sender, true);
        saleTokenTransfer(from, tokenId, nooftoken);
    }


// Note that address recovered from signatures must be strictly increasing, in order to prevent duplicates

function execute(uint8[] memory sigV, bytes32[] memory sigR, bytes32[] memory sigS, address destination, uint value, bytes memory data, address executor, uint gasLimit) public {
        require(sigR.length == threshold);
        require(sigR.length == sigS.length && sigR.length == sigV.length);
        require(executor == msg.sender || executor == address(0));

        bytes32 txInputHash = keccak256(abi.encodePacked(
            address(this),
            TXTYPE_HASH,
            SALT,
            destination,
            value,
            keccak256(data),
            nonce,
            executor,
            gasLimit
        ));
        bytes32 totalHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", txInputHash));

        address lastAdd = address(0); // cannot have address(0) as an owner
        for (uint i = 0; i < threshold; i++) {
            address recovered = ecrecover(totalHash, sigV[i], sigR[i], sigS[i]);
            require(recovered > lastAdd && isOwner[recovered]);
            lastAdd = recovered;
        }

        // If we make it here all signatures are accounted for.
        // The address.call() syntax is no longer recommended, see:
        // https://github.com/ethereum/solidity/issues/2884
        nonce = nonce + 1;
        bool success = false;
        assembly { success := call(gasLimit, destination, value, add(data, 0x20), mload(data), 0, 0) }
        require(success);
    }


// Process a transfer internally.

function xfer(address _from, address _to, uint _amount)
        internal
        returns (bool)
    {
        require(_amount <= balances[_from]);

        Transfer(_from, _to, _amount);

        // avoid wasting gas on 0 token transfers
        if(_amount == 0) return true;

        balances[_from] = balances[_from].sub(_amount);
        balances[_to]   = balances[_to].add(_amount);

        return true;
    }


// Calculates the sale and wholesale portion of tokens for a given value
// of wei at the time of calling.

function ethToTokens(uint _wei)
        public
        view
        returns (uint allTokens_, uint wholesaleTokens_)
    {
        // Get wholesale portion of ether and tokens
        uint wsValueLeft = wholeSaleValueLeft();
        uint wholesaleSpend =
                fundFailed() ? 0 :
                icoSucceeded ? 0 :
                now < START_DATE ? 0 :
                now > END_DATE ? 0 :
                // No wholesale purchse
                _wei < WHOLESALE_THRESHOLD ? 0 :
                // Total wholesale purchase
                _wei < wsValueLeft ?  _wei :
                // over funded for remaining wholesale tokens
                wsValueLeft;

        wholesaleTokens_ = wholesaleSpend
                .mul(RATE_WHOLESALE)
                .mul(TOKEN)
                .div(1 ether);

        // Remaining wei used to purchase retail tokens
        _wei = _wei.sub(wholesaleSpend);

        // Get retail rate
        uint saleRate = currentRate();

        allTokens_ = _wei
                .mul(saleRate)
                .mul(TOKEN)
                .div(1 ether)
                .add(wholesaleTokens_);
    }


// General addresses can purchase tokens during funding

function proxyPurchase(address _addr)
        public
        payable
        noReentry
        returns (bool)
    {
        require(!fundFailed());
        require(!icoSucceeded);
        require(now > START_DATE);
        require(now <= END_DATE);
        require(msg.value > 0);

        // Log ether deposit
        Deposit (_addr, msg.value);

        // Get ether to token conversion
        uint tokens;
        // Portion of tokens sold at wholesale rate
        uint wholesaleTokens;

        (tokens, wholesaleTokens) = ethToTokens(msg.value);

        // Block any failed token creation
        require(tokens > 0);

        // Prevent over subscribing
        require(balances[HUT34_RETAIN] - tokens >= RETAINED_TOKENS);

        // Adjust wholesale tokens left for sale
        if (wholesaleTokens != 0) {
            wholesaleLeft = wholesaleLeft.sub(wholesaleTokens);
        }

        // transfer tokens from fund wallet
        balances[HUT34_RETAIN] = balances[HUT34_RETAIN].sub(tokens);
        balances[_addr] = balances[_addr].add(tokens);
        Transfer(HUT34_RETAIN, _addr, tokens);

        // Update funds raised
        etherRaised = etherRaised.add(msg.value);

        // Update holder payments
        etherContributed[_addr] = etherContributed[_addr].add(msg.value);

        // Check KYC requirement
        if(etherContributed[_addr] >= KYC_THRESHOLD && !mustKyc[_addr]) {
            mustKyc[_addr] = true;
            Kyc(_addr, true);
        }

        return true;
    }


// Owner can sweep a successful funding to the fundWallet.
// Can be called repeatedly to recover errant ether which may have been
// `selfdestructed` to the contract
// Contract can be aborted up until this returns `true`

function finalizeICO()
        public
        onlyOwner
        preventReentry()
        returns (bool)
    {
        // Must have reached minimum cap
        require(fundRaised());

        // Set first vesting date (only once as this function can be called again)
        if(!icoSucceeded) {
            nextReleaseDate = now + VESTING_PERIOD;
        }

        // Set success flag;
        icoSucceeded = true;

        // Transfer % Developer commission
        uint devCommission = calcCommission();
        Withdrawal(this, COMMISSION_WALLET, devCommission);
        COMMISSION_WALLET.transfer(devCommission);

        // Remaining % to the fund wallet
        Withdrawal(this, HUT34_WALLET, this.balance);
        HUT34_WALLET.transfer(this.balance);
        return true;
    }


// Bulk refunds can be pushed from a failed ICO

function refundFor(address[] _addrs)
        public
        preventReentry()
        returns (bool)
    {
        require(fundFailed());
        uint i;
        uint len = _addrs.length;
        uint value;
        uint tokens;
        address addr;

        for (i; i < len; i++) {
            addr = _addrs[i];
            value = etherContributed[addr];
            tokens = balances[addr];
            if (tokens > 0) {
                // Return tokens
                // transfer tokens from fund wallet
                balances[HUT34_RETAIN] = balances[HUT34_RETAIN].add(tokens);
                delete balances[addr];
                Transfer(addr, HUT34_RETAIN, tokens);
            }

            if (value > 0) {
                // Refund ether contribution
                delete etherContributed[addr];
                delete mustKyc[addr];
                refunded = refunded.add(value);
                Withdrawal(this, addr, value);
                addr.transfer(value);
            }
        }
        return true;
    }


//
// ERC20 additional and overloaded functions
//
// Allows a sender to transfer tokens to an array of recipients

function transferToMany(address[] _addrs, uint[] _amounts)
        public
        noReentry
        returns (bool)
    {
        require(_addrs.length == _amounts.length);
        uint len = _addrs.length;
        for(uint i = 0; i < len; i++) {
            xfer(msg.sender, _addrs[i], _amounts[i]);
        }
        return true;
    }


// This will selfdestruct the contract on the condittion all have been
// processed.

function destroy()
        public
        noReentry
        onlyOwner
    {
        require(!__abortFuse);
        require(refunded == (etherRaised - PRESALE_ETH_RAISE));
        // Log burned tokens for complete ledger accounting on archival nodes
        Transfer(HUT34_RETAIN, 0x0, balances[HUT34_RETAIN]);
        Transfer(HUT34_VEST_ADDR, 0x0, VESTED_TOKENS);
        Transfer(PRESOLD_ADDRESS, 0x0, PRESOLD_TOKENS);
        // Garbage collect mapped state
        delete balances[HUT34_RETAIN];
        delete balances[PRESOLD_ADDRESS];
        selfdestruct(owner);
    }


// Calculate commission on prefunded and raised ether.

function calcCommission()
        internal
        view
        returns(uint)
    {
        uint commission = (this.balance + PRESALE_ETH_RAISE) / COMMISSION_DIV;
        // Edge case that prefund causes commission to be greater than balance
        return commission <= this.balance ? commission : this.balance;
    }


/// @notice Check whether an address is a regular address or not.
/// @param _addr Address of the contract that has to be checked
/// @return `true` if `_addr` is a regular address (not a contract)

function isRegularAddress(address _addr) internal view returns(bool) {
        if (_addr == address(0)) { return false; }
        uint size;
        assembly { size := extcodesize(_addr) } // solium-disable-line security/no-inline-assembly
        return size == 0;
    }


/// @notice Helper function actually performing the sending of tokens.
/// @param _operator The address performing the send
/// @param _from The address holding the tokens being sent
/// @param _to The address of the recipient
/// @param _amount The number of tokens to be sent
/// @param _data Data generated by the user to be passed to the recipient
/// @param _operatorData Data generated by the operator to be passed to the recipient
/// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
///  implementing `ERC777tokensRecipient`.
///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
///  functions SHOULD set this parameter to `false`.

function _send(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData,
        bool _preventLocking
    )
        internal
    {
        callSender(_operator, _from, _to, _amount, _data, _operatorData);

        super._transfer(_from, _to, _amount); // TODO test it does not call this._transfer (which would emit 2 Sent event)

        callRecipient(_operator, _from, _to, _amount, _data, _operatorData, _preventLocking);

        emit Sent(_operator, _from, _to, _amount, _data, _operatorData);
    }


/// @notice Helper function that checks for ERC777TokensRecipient on the recipient and calls it.
///  May throw according to `_preventLocking`
/// @param _operator The address performing the send or mint
/// @param _from The address holding the tokens being sent
/// @param _to The address of the recipient
/// @param _amount The number of tokens to be sent
/// @param _data Data generated by the user to be passed to the recipient
/// @param _operatorData Data generated by the operator to be passed to the recipient
/// @param _preventLocking `true` if you want this function to throw when tokens are sent to a contract not
///  implementing `ERC777TokensRecipient`.
///  ERC777 native Send functions MUST set this parameter to `true`, and backwards compatible ERC20 transfer
///  functions SHOULD set this parameter to `false`.

function callRecipient(
        address _operator,
        address _from,
        address _to,
        uint256 _amount,
        bytes memory _data,
        bytes memory _operatorData,
        bool _preventLocking
    )
        internal
    {
        address recipientImplementation = interfaceAddr(_to, "ERC777TokensRecipient");
        if (recipientImplementation != address(0)) {
            ERC777TokensRecipient(recipientImplementation).tokensReceived(
                _operator, _from, _to, _amount, _data, _operatorData);
        } else if (_preventLocking) {
            require(isRegularAddress(_to), "Cannot send to contract without ERC777TokensRecipient");
        }
    }


/// @notice Get exchange dynamic fee related keys
/// @return threshold, weight decay, rounds, and max fee

function getExchangeDynamicFeeConfig() internal view returns (DynamicFeeConfig memory) {
        bytes32[] memory keys = new bytes32[](4);
        keys[0] = SETTING_EXCHANGE_DYNAMIC_FEE_THRESHOLD;
        keys[1] = SETTING_EXCHANGE_DYNAMIC_FEE_WEIGHT_DECAY;
        keys[2] = SETTING_EXCHANGE_DYNAMIC_FEE_ROUNDS;
        keys[3] = SETTING_EXCHANGE_MAX_DYNAMIC_FEE;
        uint[] memory values = flexibleStorage().getUIntValues(SETTING_CONTRACT_NAME, keys);
        return DynamicFeeConfig({threshold: values[0], weightDecay: values[1], rounds: values[2], maxFee: values[3]});
    }


// solhint-disable no-complex-fallback

function() external payable {
        // Mutable call setting Proxyable.messageSender as this is using call not delegatecall
        target.setMessageSender(msg.sender);

        assembly {
            let free_ptr := mload(0x40)
            calldatacopy(free_ptr, 0, calldatasize)

            /* We must explicitly forward ether to the underlying contract as well. */
            let result := call(gas, sload(target_slot), callvalue, free_ptr, calldatasize, 0, 0)
            returndatacopy(free_ptr, 0, returndatasize)

            if iszero(result) {
                revert(free_ptr, returndatasize)
            }
            return(free_ptr, returndatasize)
        }
    }


/**
 * SIP-139
 * resets the stored value for _lastExchangeRate for multiple currencies to the latest rate
 * can be used to un-suspend synths after a suspension happenned
 * doesn't check deviations here, so believes that owner knows better
 * emits LastRateOverriden
 */

function resetLastExchangeRate(bytes32[] calldata currencyKeys) external onlyOwner {
        (uint[] memory rates, bool anyRateInvalid) = _exchangeRates().ratesAndInvalidForCurrencies(currencyKeys);

        require(!anyRateInvalid, "Rates for given synths not valid");

        for (uint i = 0; i < currencyKeys.length; i++) {
            emit LastRateOverriden(currencyKeys[i], _lastExchangeRate[currencyKeys[i]], rates[i]);
            _lastExchangeRate[currencyKeys[i]] = rates[i];
        }
    }


/**
 * Rate is invalid if:
 * - is outside of deviation bounds relative to previous non-zero rate
 * - (warm up case) if previous rate was 0 (init), gets last 4 rates from oracle, and checks
 * if rate is outside of deviation w.r.t any of the 3 previous ones (excluding the last one).
 */

function _isRateOutOfBounds(bytes32 currencyKey, uint currentRate) internal view returns (bool) {
        if (currentRate == 0) {
            return true;
        }

        uint lastRateFromExchange = _lastExchangeRate[currencyKey];

        if (lastRateFromExchange > 0) {
            return _isDeviationAboveThreshold(lastRateFromExchange, currentRate);
        }

        // if no last exchange for this synth, then we need to look up last 3 rates (+1 for current rate)
        (uint[] memory rates, ) = _exchangeRates().ratesAndUpdatedTimeForCurrencyLastNRounds(currencyKey, 4, 0);

        // start at index 1 to ignore current rate
        for (uint i = 1; i < rates.length; i++) {
            // ignore any empty rates in the past (otherwise we will never be able to get validity)
            if (rates[i] > 0 && _isDeviationAboveThreshold(rates[i], currentRate)) {
                return true;
            }
        }

        return false;
    }


/**
 * @notice Gets the strike price by multiplying the current underlying price
 * with a multiplier
 * @param expiryTimestamp is the unix timestamp of expiration
 * @param isPut is whether option is put or call
 * @return newStrikePrice is the strike price of the option (ex: for BTC might be 45000 * 10 ** 8)
 * @return newDelta will be set to zero for percent strike selection
 */

function getStrikePrice(uint256 expiryTimestamp, bool isPut)
        external
        view
        returns (uint256 newStrikePrice, uint256 newDelta)
    {
        require(
            expiryTimestamp > block.timestamp,
            "Expiry must be in the future!"
        );

        // asset price
        uint256 strikePrice =
            optionsPremiumPricer.getUnderlyingPrice().mul(strikeMultiplier).div(
                STRIKE_MULTIPLIER
            );

        newStrikePrice = isPut
            ? strikePrice.sub(strikePrice % step)
            : strikePrice.add(step.sub(strikePrice % step));

        newDelta = 0;
    }


// TODO could potentialy get ir fo this and use operatorSend instead (the token contract itself could be an defaultOperator)

function sendFrom(address _from, address _to, uint256 _amount, bytes calldata _data) external {
        require(msg.sender == address(this), "only to be used by contract to support meta-tx"); // allow _from to allow gas estimatiom
        _send(_from, _from, _to, _amount, _data, "", true);
    }


/* Transfer the balance from the sender's address to the address _to */

function transfer(address _to, uint256 _value) public returns (bool success) {
        if (balances[msg.sender] >= _value
            && _value > 0
            && balances[_to] + _value > balances[_to]) {
			balances[msg.sender] = balances[msg.sender].sub(_value);
            balances[_to] = balances[_to].add(_value);
            Transfer(msg.sender, _to, _value);
            return true;
        } else {
            return false;
        }
    }


/* Withdraws to address _to form the address _from up to the amount _value */

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        if (balances[_from] >= _value
            && allowed[_from][msg.sender] >= _value
            && _value > 0
            && balances[_to] + _value > balances[_to]) {
            balances[_from] = balances[_from].sub(_value);
            allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
            balances[_to] = balances[_to].add(_value);
            Transfer(_from, _to, _value);
            return true;
        } else {
            return false;
        }
    }


// Only the liqMiningEmergencyHandler can call this function, when its in emergencyMode
// this will allow a spender to spend the whole balance of the specified tokens from this contract
// as well as to spend tokensForLpHolder from the respective lp holders for expiries specified
// the spender should ideally be a contract with logic for users to withdraw out their funds.

function setUpEmergencyMode(uint256[] calldata expiries, address spender) external override {
        (, bool emergencyMode) = pausingManager.checkLiqMiningStatus(address(this));
        require(emergencyMode, "NOT_EMERGENCY");

        (address liqMiningEmergencyHandler, , ) = pausingManager.liqMiningEmergencyHandler();
        require(msg.sender == liqMiningEmergencyHandler, "NOT_EMERGENCY_HANDLER");

        for (uint256 i = 0; i < expiries.length; i++) {
            IPendleLpHolder(expiryData[expiries[i]].lpHolder).setUpEmergencyMode(spender);
        }
        IERC20(pendleTokenAddress).approve(spender, type(uint256).max);
    }


/**
 * @notice fund new epochs
 * @dev Once the last epoch is over, the program is permanently override
 * @dev the settings must be set before epochs can be funded
 => if funded=true, means that epochs have been funded & have already has valid allocation settings
 * conditions:
 * Must only be called by governance
 */

function fund(uint256[] calldata _rewards) external override onlyGovernance {
        checkNotPaused();
        // Can only be fund if there is already a setting
        require(latestSetting.id > 0, "NO_ALLOC_SETTING");
        // Once the program is over, cannot fund
        require(_getCurrentEpochId() <= numberOfEpochs, "LAST_EPOCH_OVER");

        uint256 nNewEpochs = _rewards.length;
        uint256 totalFunded;
        // all the funding will be used for new epochs
        for (uint256 i = 0; i < nNewEpochs; i++) {
            totalFunded = totalFunded.add(_rewards[i]);
            epochData[numberOfEpochs + i + 1].totalRewards = _rewards[i];
        }

        require(totalFunded > 0, "ZERO_FUND");
        funded = true;
        numberOfEpochs = numberOfEpochs.add(nNewEpochs);
        IERC20(pendleTokenAddress).safeTransferFrom(msg.sender, address(this), totalFunded);
        emit Funded(_rewards, numberOfEpochs);
    }


/**
 @notice top up rewards for any funded future epochs (but not to create new epochs)
 * conditions:
 * Must only be called by governance
 * The contract must have been funded already
 */

function topUpRewards(uint256[] calldata _epochIds, uint256[] calldata _rewards)
        external
        override
        onlyGovernance
        isFunded
    {
        checkNotPaused();
        require(latestSetting.id > 0, "NO_ALLOC_SETTING");
        require(_epochIds.length == _rewards.length, "INVALID_ARRAYS");

        uint256 curEpoch = _getCurrentEpochId();
        uint256 endEpoch = numberOfEpochs;
        uint256 totalTopUp;

        for (uint256 i = 0; i < _epochIds.length; i++) {
            require(curEpoch < _epochIds[i] && _epochIds[i] <= endEpoch, "INVALID_EPOCH_ID");
            totalTopUp = totalTopUp.add(_rewards[i]);
            epochData[_epochIds[i]].totalRewards = epochData[_epochIds[i]].totalRewards.add(
                _rewards[i]
            );
        }

        require(totalTopUp > 0, "ZERO_FUND");
        IERC20(pendleTokenAddress).safeTransferFrom(msg.sender, address(this), totalTopUp);
        emit RewardsToppedUp(_epochIds, _rewards);
    }


/**
 @notice set a new allocation setting, which will be applied from the next epoch onwards
 @dev  all the epochData from latestSetting.firstEpochToApply+1 to current epoch will follow the previous
 allocation setting
 @dev We must set the very first allocation setting before the start of epoch 1,
 otherwise epoch 1 will not have any allocation setting!
 In that case, we will not be able to set any allocation and hence its not possible to
 fund the contract as well
 => We should just throw this contract away, and funds are SAFU!
 @dev the length of _expiries array is expected to be small, 2 or 3
 @dev it's intentional that we don't check the existence of expiries
 */

function setAllocationSetting(
        uint256[] calldata _expiries,
        uint256[] calldata _allocationNumerators
    ) external onlyGovernance {
        checkNotPaused();
        require(_expiries.length == _allocationNumerators.length, "INVALID_ALLOCATION");
        if (latestSetting.id == 0) {
            require(block.timestamp < startTime, "LATE_FIRST_ALLOCATION");
        }

        uint256 curEpoch = _getCurrentEpochId();
        // set the settingId for past epochs
        for (uint256 i = latestSetting.firstEpochToApply; i <= curEpoch; i++) {
            epochData[i].settingId = latestSetting.id;
        }

        // create a new setting that will be applied from the next epoch onwards
        latestSetting.firstEpochToApply = curEpoch + 1;
        latestSetting.id++;

        uint256 sumAllocationNumerators;
        for (uint256 _i = 0; _i < _expiries.length; _i++) {
            allocationSettings[latestSetting.id][_expiries[_i]] = _allocationNumerators[_i];
            sumAllocationNumerators = sumAllocationNumerators.add(_allocationNumerators[_i]);
        }
        require(sumAllocationNumerators == ALLOCATION_DENOMINATOR, "INVALID_ALLOCATION");
        emit AllocationSettingSet(_expiries, _allocationNumerators);
    }


/**
 * @notice Similar to stake() function, but using a permit to approve for LP tokens first
 */

function stakeWithPermit(
        uint256 expiry,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    )
        external
        override
        isFunded
        nonReentrant
        nonContractOrWhitelisted
        returns (address newLpHoldingContractAddress)
    {
        checkNotPaused();
        address xyt = address(data.xytTokens(forgeId, underlyingAsset, expiry));
        address marketAddress = data.getMarket(marketFactoryId, xyt, baseToken);
        // Pendle Market LP tokens are EIP-2612 compliant, hence we can approve liq-mining contract using a signature
        IPendleYieldToken(marketAddress).permit(
            msg.sender,
            address(this),
            amount,
            deadline,
            v,
            r,
            s
        );

        newLpHoldingContractAddress = _stake(expiry, amount);
    }


/**
 * @notice Use to withdraw their LP from a specific expiry
 Conditions:
 * only be called if the contract has been funded.
 * must have Reentrancy protection
 * only be called if 0 < current epoch (always can withdraw)
 */

function withdraw(uint256 expiry, uint256 amount) external override nonReentrant isFunded {
        checkNotPaused();
        uint256 curEpoch = _getCurrentEpochId();
        require(curEpoch > 0, "NOT_STARTED");
        require(amount != 0, "ZERO_AMOUNT");

        ExpiryData storage exd = expiryData[expiry];
        require(exd.balances[msg.sender] >= amount, "INSUFFICIENT_BALANCE");

        _pushLpToken(expiry, amount);
        emit Withdrawn(expiry, msg.sender, amount);
    }


/**
 * @notice use to claim PENDLE rewards
 Conditions:
 * only be called if the contract has been funded.
 * must have Reentrancy protection
 * only be called if 0 < current epoch (always can withdraw)
 * Anyone can call it (and claim it for any other user)
 */

function redeemRewards(uint256 expiry, address user)
        external
        override
        isFunded
        nonReentrant
        returns (uint256 rewards)
    {
        checkNotPaused();
        uint256 curEpoch = _getCurrentEpochId();
        require(curEpoch > 0, "NOT_STARTED");
        require(user != address(0), "ZERO_ADDRESS");
        require(userExpiries[user].hasExpiry[expiry], "INVALID_EXPIRY");

        rewards = _beforeTransferPendingRewards(expiry, user);
        if (rewards != 0) {
            IERC20(pendleTokenAddress).safeTransfer(user, rewards);
        }
    }


/**
 @notice update the following stake data for the current epoch:
 - epochData[_curEpoch].stakeUnitsForExpiry
 - epochData[_curEpoch].lastUpdatedForExpiry
 @dev If this is the very first transaction involving this expiry, then need to update for the
 previous epoch as well. If the previous didn't have any transactions at all, (and hence was not
 updated at all), we need to update it and check the previous previous ones, and so on..
 @dev must be called right before every _updatePendingRewards()
 @dev this is the only function that updates lastTimeUserStakeUpdated & stakeUnitsForExpiry
 @dev other functions must make sure that totalStakeLPForExpiry could be assumed
 to stay exactly the same since lastTimeUserStakeUpdated until now;
 @dev to be called only by _updatePendingRewards
 */

function _updateStakeDataForExpiry(uint256 expiry) internal {
        uint256 _curEpoch = _getCurrentEpochId();

        // loop through all epochData in descending order
        for (uint256 i = Math.min(_curEpoch, numberOfEpochs); i > 0; i--) {
            uint256 epochEndTime = _endTimeOfEpoch(i);
            uint256 lastUpdatedForEpoch = epochData[i].lastUpdatedForExpiry[expiry];

            if (lastUpdatedForEpoch == epochEndTime) {
                break; // its already updated until this epoch, our job here is done
            }

            // if the epoch hasn't been fully updated yet, we will update it
            // just add the amount of units contributed by users since lastUpdatedForEpoch -> now
            // by calling _calcUnitsStakeInEpoch
            epochData[i].stakeUnitsForExpiry[expiry] = epochData[i].stakeUnitsForExpiry[expiry]
                .add(
                _calcUnitsStakeInEpoch(expiryData[expiry].totalStakeLP, lastUpdatedForEpoch, i)
            );
            // If the epoch has ended, lastUpdated = epochEndTime
            // If not yet, lastUpdated = block.timestamp (aka now)
            epochData[i].lastUpdatedForExpiry[expiry] = Math.min(block.timestamp, epochEndTime);
        }
    }


// calc the amount of rewards the user is eligible to receive from this epoch
// but we will return the amount per vestingEpoch instead

function _calcAmountRewardsForUserInEpoch(
        uint256 expiry,
        address user,
        uint256 epochId
    ) internal view returns (uint256 rewardsPerVestingEpoch) {
        uint256 settingId =
            epochId >= latestSetting.firstEpochToApply
                ? latestSetting.id
                : epochData[epochId].settingId;

        uint256 rewardsForThisExpiry =
            epochData[epochId].totalRewards.mul(allocationSettings[settingId][expiry]).div(
                ALLOCATION_DENOMINATOR
            );

        rewardsPerVestingEpoch = rewardsForThisExpiry
            .mul(epochData[epochId].stakeUnitsForUser[user][expiry])
            .div(epochData[epochId].stakeUnitsForExpiry[expiry])
            .div(vestingEpochs);
    }


/**
 * @notice returns the stakeUnits in the _epochId(th) epoch of an user if he stake from _startTime to now
 * @dev to calculate durationStakeThisEpoch:
 *   user will stake from _startTime -> _endTime, while the epoch last from _startTimeOfEpoch -> _endTimeOfEpoch
 *   => the stakeDuration of user will be min(_endTime,_endTimeOfEpoch) - max(_startTime,_startTimeOfEpoch)
 */

function _calcUnitsStakeInEpoch(
        uint256 lpAmount,
        uint256 _startTime,
        uint256 _epochId
    ) internal view returns (uint256 stakeUnitsForUser) {
        uint256 _endTime = block.timestamp;

        uint256 _l = Math.max(_startTime, _startTimeOfEpoch(_epochId));
        uint256 _r = Math.min(_endTime, _endTimeOfEpoch(_epochId));
        uint256 durationStakeThisEpoch = _r.subMax0(_l);

        return lpAmount.mul(durationStakeThisEpoch);
    }


/**
 @notice Calc the amount of rewards that the user can receive now.
 @dev To be called before any rewards is transferred out
 */

function _beforeTransferPendingRewards(uint256 expiry, address user)
        internal
        returns (uint256 amountOut)
    {
        _updatePendingRewards(expiry, user);

        uint256 _lastEpoch = Math.min(_getCurrentEpochId(), numberOfEpochs + vestingEpochs);
        for (uint256 i = expiryData[expiry].lastEpochClaimed[user]; i <= _lastEpoch; i++) {
            if (epochData[i].availableRewardsForUser[user] > 0) {
                amountOut = amountOut.add(epochData[i].availableRewardsForUser[user]);
                epochData[i].availableRewardsForUser[user] = 0;
            }
        }

        expiryData[expiry].lastEpochClaimed[user] = _lastEpoch;
        emit PendleRewardsSettled(expiry, user, amountOut);
        return amountOut;
    }


/**
 * @dev all LP interests related functions are almost identical to markets' functions
 * Very similar to the function in PendleMarketBase. Any major differences are likely to be bugs
 Please refer to it for more details
 */

function _updateParamL(uint256 expiry) internal {
        ExpiryData storage exd = expiryData[expiry];

        if (!checkNeedUpdateParamL(expiry)) return;

        IPendleLpHolder(exd.lpHolder).redeemLpInterests();

        uint256 currentNYield = IERC20(underlyingYieldToken).balanceOf(exd.lpHolder);
        (uint256 firstTerm, uint256 paramR) = _getFirstTermAndParamR(expiry, currentNYield);

        uint256 secondTerm;

        if (exd.totalStakeLP != 0) secondTerm = paramR.mul(MULTIPLIER).div(exd.totalStakeLP);

        // Update new states
        exd.paramL = firstTerm.add(secondTerm);
        exd.lastNYield = currentNYield;
    }


/**
 * @dev Creates a new voting proposal for the execution `_transaction` of `_targetContract`.
 * Only voter can create a new proposal.
 *
 * Emits a `ProposalStarted` event.
 *
 * Requirements:
 *
 * - `_targetContract` cannot be the zero address.
 * - `_transaction` length must not be less than 4 bytes.
 *
 * @notice Create a new voting proposal for the execution `_transaction` of `_targetContract`. You must be voter.
 * @param _targetContract Target contract address that can execute target `_transaction`
 * @param _transaction Target transaction to execute
 */

function newProposal( address _targetContract, bytes memory _transaction ) public onlyVoter {
        require(_targetContract != address(0), "Address must be non-zero");
        require(_transaction.length >= 4, "Tx must be 4+ bytes");
        // solhint-disable not-rely-on-time
        bytes32 _proposalHash = keccak256(abi.encodePacked(_targetContract, _transaction, now));
        require(proposals[_proposalHash].transaction.length == 0, "The poll has already been initiated");
        proposals[_proposalHash].targetContract = _targetContract;
        proposals[_proposalHash].transaction = _transaction;
        proposalsHashes.push(_proposalHash);
        proposalsCount = proposalsCount.add(1);
        emit ProposalStarted(_proposalHash);
    }


/**
 * @dev Adds sender's vote to the proposal and then follows the majority voting algoritm.
 *
 * Emits a `Vote` event.
 *
 * Requirements:
 *
 * - proposal with `_proposalHash` must not be finished.
 * - sender must not be already voted.
 *
 * @notice Vote "for" or "against" in proposal with `_proposalHash` hash.
 * @param _proposalHash Unique mapping key of proposal
 * @param _yes 1 is vote "for" and 0 is "against"
 */

function vote(bytes32 _proposalHash, bool _yes) public onlyVoter { // solhint-disable code-complexity
        require(!proposals[_proposalHash].finished, "Already finished");
        require(!proposals[_proposalHash].votedFor[msg.sender], "Already voted");
        require(!proposals[_proposalHash].votedAgainst[msg.sender], "Already voted");
        if (_yes) {
            proposals[_proposalHash].yesVotes = proposals[_proposalHash].yesVotes.add(1);
            proposals[_proposalHash].votedFor[msg.sender] = true;
        } else {
            proposals[_proposalHash].noVotes = proposals[_proposalHash].noVotes.add(1);
            proposals[_proposalHash].votedAgainst[msg.sender] = true;
        }
        emit Vote(
            _proposalHash,
            _yes,
            proposals[_proposalHash].yesVotes,
            proposals[_proposalHash].noVotes,
            votersCount
        );
        if (proposals[_proposalHash].yesVotes > votersCount.div(2)) {
            executeProposal(_proposalHash);
            finishProposal(_proposalHash);
        } else if (proposals[_proposalHash].noVotes >= (votersCount + 1).div(2)) {
            finishProposal(_proposalHash);
        }
    }


/**
 * @dev Removes `_address` from the voters list.
 * This method can be executed only via proposal of this Governance contract.
 *
 * Emits a `delVoter` event.
 *
 * Requirements:
 *
 * - `_address` must be in voters list.
 * - Num of voters must be more than one.
 *
 * @param _address Address of voter to delete
 */

function delVoter(address _address) public onlyMe {
        require(isVoter[_address], "Not in voters list");
        require(votersCount > 1, "Can not delete single voter");
        for (uint256 i = 0; i < voters.length; i++) {
            if (voters[i] == _address) {
                if (voters.length > 1) {
                    voters[i] = voters[voters.length - 1];
                }
                voters.length--; // Implicitly recovers gas from last element storage
                isVoter[_address] = false;
                votersCount = votersCount.sub(1);
                emit VoterDeleted(_address);
                break;
            }
        }
    }


// payout for claiming

function payouts(address payable _to, uint256 _amount, address token) external onlyOperator {
         require(_to != address(0),"_to is zero");
        if (token != ETHEREUM) {
            IERC20(token).safeTransfer(_to, _amount);
        } else {
            _to.transfer(_amount);
        }

        emit ePayouts(_to, _amount);
    }


// burn 1/2 for claiming

function burnOuts(address[] calldata _burnUsers, uint256[] calldata _amounts)
        external onlyOperator
    {
        require(_burnUsers.length == _amounts.length, "not equal");

        for(uint256 i = 0; i<_burnUsers.length; i++) {
            require(_balances[_burnUsers[i]] >= _amounts[i], "insufficient");

            _balances[_burnUsers[i]] = _balances[_burnUsers[i]].sub(_amounts[i]);
            Nsure.burn(_amounts[i]);

            emit Burn(_burnUsers[i],_amounts[i]);
        }
    }


/*
 *       FEE:
 *              1 = 0.001%
 *           1000 = 1%
 *         100000 = 100%
 */

function setFeeDistributions(address _token, address _feeWallet, string memory _name, uint256 _percent) public  onlyOwner {
        require(_token != address(0), "address not valid");
        require(_feeWallet != address(0), "address not valid");

        _Fee[] storage fees = feeDistributions[_token];
        // uint256 feesCount = fees.length;
        uint256 feesCount = getFeeDistributionsCount(_token);

        bool feeExiste = false;

        uint totalFeePercent = getTotalFeePercent (_token);
        totalFeePercent = totalFeePercent.add(_percent);
        require(totalFeePercent <= 100000, "total fee cannot exceed 100");

        for (uint i = 0; i < feesCount; i++){
            if (fees[i].wallet == _feeWallet){
                fees[i].name    = _name;
                fees[i].percent = _percent;
                fees[i].active  = true;

                feeExiste = true;
                break;
            }
        }

        // fee not found => add as new fee
        if (!feeExiste){
            _Fee memory fee;

            fee.id = (feesCount + 1);
            fee.name = _name;
            fee.wallet = _feeWallet;
            fee.percent = _percent;
            fee.active = true;

            fees.push(fee);
        }
    }


/**
 * @dev users can invoke this function to open a new pool.
 * @param _token the ERC20 token of the reward.
 * @param _reward the amount of reward.
 * @param _weight determines how the pool ranks in frontend.
 * @param _expiry the timestamp the pool expires. when a pool expires
 * the owner will have to pay to renew it or it won't accept new entries.
 **/

function openNewPool (IERC20 _token, address _currencyToken, uint112 _exchangeRate,
    uint _reward, uint _weight, uint64 _expiry, bytes32 name) external returns (uint pid){

    // require (_weight >= minPoolCost, "Need more YouBetToken");

    address poolOwner = msg.sender;

    _reward = _safeTokenTransfer(poolOwner, address(_token), _reward, address(0));

    if(_weight>0) {
      YouBetToken.safeTransferFrom(poolOwner, address(this), _weight);
      YouBetToken.safeTransfer(feeTo, _weight);
    }

    pid = _openNewPool(poolOwner, _token, _currencyToken, _exchangeRate,
      _reward, _weight, _expiry);
    if(name != ''){
      _renamePool(pid, name);
    }
  }


/**
 * @dev users can invoke this function to purchase tokens.
 * @param _pid the pool id where the user is purchasing
 * @param _purchaseAmount amount in token to purchase
 **/

function buyTokenWithETH (uint _pid, uint _purchaseAmount,
    address referrer) external payable {
    address _customer = msg.sender;
    require (pools[_pid].status == PoolStatus.OPEN, "lottery pool not open");
    require (now < pools[_pid].expiry, "lottery pool expired");

    IERC20 _pooltoken = IERC20(pools[_pid].token);
    address _poolCurrencyToken = pools[_pid].currencyToken;

    require (_poolCurrencyToken==WETH);

    uint cost = _purchaseAmount.mul(pools[_pid].exchangeRate)/1e18;

    IWETH(WETH).deposit{value: cost}();

    if(referrer != address(0)){
      uint commission = cost.mul(perOrderCommissionRate)/1e5;
      IERC20(WETH).safeTransfer(referrer, commission);
      // will not undeflow
      cost -= commission;
      emit Commission(referrer, _customer, WETH, commission);
    }

    uint _balance = cost.add(pools[_pid].balance);
    uint _rewardBalance = _pooltoken.balanceOf(address(this));
    require(_balance <= uint112(-1) && _rewardBalance <= uint112(-1), 'OVERFLOW');
    pools[_pid].balance = uint112(_balance);

    _pooltoken.safeTransfer(_customer, _purchaseAmount);

    // will not overflow
    uint112 _newReward = uint112(uint(pools[_pid].reward).sub(_purchaseAmount));
    uint112 _rewardRealBalance = uint112(_rewardBalance);
    pools[_pid].reward = _newReward<_rewardRealBalance?_newReward:_rewardRealBalance;

    if(referrer != address(0)) {
      uint newReferrarSales = _purchaseAmount.add(poolReferrerSales[_pid][referrer]);
      require(newReferrarSales <= uint112(-1), 'OVERFLOW');
      poolReferrerSales[_pid][referrer] = newReferrarSales;
      if(newReferrarSales > pools[_pid].winnerCandidateSales){
        pools[_pid].winnerCandidateSales = uint112(newReferrarSales);
        pools[_pid].winnerCandidate = referrer;
      }

    }

    emit Purchase(_pid, _customer, _purchaseAmount,
      address(_pooltoken), _poolCurrencyToken, cost);
  }


/**
 * @dev allows anyone to draw the winning ticket for an eligible pool.
 * @param _pid the pool id where user is drawing.
 **/

function closePool (uint _pid) external {
    address _poolOwner = pools[_pid].owner;

    require (pools[_pid].status==PoolStatus.OPEN, "Pool must be open");

    require (_poolOwner==msg.sender||msg.sender==owner(), "unauthorized");

    IERC20 _pooltoken = IERC20(pools[_pid].token);
    IERC20 _poolCurrencyToken = IERC20(pools[_pid].currencyToken);
    uint _poolReward = pools[_pid].reward;
    uint _poolExchangeRate = pools[_pid].exchangeRate;
    uint _poolBalance = pools[_pid].balance;

    uint _fees = _poolBalance/100;
    uint _contestReward = _poolBalance.mul(contestRewardRate)/1e5;
    uint _poolOwnerRevenue = _poolBalance - _fees - _contestReward;

    _poolCurrencyToken.safeTransfer(_poolOwner, _poolOwnerRevenue);
    _pooltoken.safeTransfer(_poolOwner, _poolReward);

    _poolCurrencyToken.safeTransfer(feeTo, _fees);

    processContestWinner(_pid, _contestReward);

  }


// function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external {
//     _transferFrom(_from, _to, _id, _value);
// }

function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external {
        _transferFrom(_from, _to, _id, _value);
        require(_checkERC1155AndCallSafeTransfer(msg.sender == address(_sandContract) ? _from : msg.sender, _from, _to, _id, _value, _data, false, false));
    }


// function batchTransferFrom(address _from, address _to, uint256[]calldata  _ids, uint256[] calldata _values) external {
//     _batchTransferFrom(_from, _to, _ids, _values);
// }

function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external {
        _batchTransferFrom(_from, _to, _ids, _values);
        require(_checkERC1155AndCallSafeBatchTransfer(msg.sender == address(_sandContract) ? _from : msg.sender, _from, _to, _ids, _values, _data));
    }


// TODO enable ?

function extractERC721(address _sender, uint256 _id, string calldata _uri) external {
        require(msg.sender == _sender || msg.sender == address(_sandContract) || mSuperOperators[msg.sender], "require meta approval");
        require(erc1155_metadataURIHashes[_id] != 0, "Not an ERC1155 Token");
        require(erc1155_metadataURIHashes[_id] == keccak256(abi.encodePacked(_uri)), "URI hash does not match");
        _burnERC1155(_sender, _id, 1);
        address creator = creatorOf(_id);
        uint256 newTokenId = uint256(creator) * 2**(256-160) + 0 * 2**(256-160-8) + nextSubId;
        nextSubId++;
        _mint(_uri, 1, creator, _sender, newTokenId, "");
        emit Extraction(_id, newTokenId, _uri);
    }


/**
 * @dev transfer token for a specified address with froze status checking
 * @param _toMulti The addresses to transfer to.
 * @param _values The array of the amount to be transferred.
 */

function transferMultiAddress(address[] _toMulti, uint256[] _values) public whenNotPaused returns (bool) {
    require(!frozenAccount[msg.sender]);
    assert(_toMulti.length == _values.length);

    uint256 i = 0;
    while ( i < _toMulti.length) {
        require(_toMulti[i] != address(0));
        require(_values[i] <= balances[msg.sender]);

        // SafeMath.sub will throw if there is not enough balance.
        balances[msg.sender] = balances[msg.sender].sub(_values[i]);
        balances[_toMulti[i]] = balances[_toMulti[i]].add(_values[i]);
        Transfer(msg.sender, _toMulti[i], _values[i]);

        i = i.add(1);
    }

    return true;
  }


/**
 * @dev Transfer tokens from one address to another with checking the frozen status
 * @param _from address The address which you want to send tokens from
 * @param _toMulti address[] The addresses which you want to transfer to in boundle
 * @param _values uint256[] the array of amount of tokens to be transferred
 */

function transferMultiAddressFrom(address _from, address[] _toMulti, uint256[] _values) public whenNotPaused returns (bool) {
    require(!frozenAccount[_from]);
    assert(_toMulti.length == _values.length);

    uint256 i = 0;
    while ( i < _toMulti.length) {
        require(_toMulti[i] != address(0));
        require(_values[i] <= balances[_from]);
        require(_values[i] <= allowed[_from][msg.sender]);

        // SafeMath.sub will throw if there is not enough balance.
        balances[_from] = balances[_from].sub(_values[i]);
        balances[_toMulti[i]] = balances[_toMulti[i]].add(_values[i]);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_values[i]);
        Transfer(_from, _toMulti[i], _values[i]);

        i = i.add(1);
    }

    return true;
  }


// Tokens sold by crowdsale contract will be frozen ultil crowdsale ends

function txAllowed(address sender, uint256 amount) private returns (bool isAllowed) {
        if (timelock[sender] > block.timestamp) {
            return isBalanceFree(sender, amount);
        } else {
            if (frozenBalances[sender] > 0) frozenBalances[sender] = 0;
            return true;
        }

    }


/*
 Current code intentionally prevents governance re-initialization.
 This may be a problem in an upgrade situation, in a case that the upgrade-to implementation
 performs an initialization (for real) and within that calls initGovernance().

 Possible workarounds:
 1. Clearing the governance info altogether by changing the MAIN_GOVERNANCE_INFO_TAG.
 This will remove existing main governance information.
 2. Modify the require part in this function, so that it will exit quietly
 when trying to re-initialize (uncomment the lines below).
 */

function initGovernance()
        internal
    {
        string memory tag = getGovernanceTag();
        GovernanceInfoStruct storage gub = governanceInfo[tag];
        // TODO(Remo,01/09/2021): Consider un-commenting lines below.
        // if (gub.initialized) {
        //     return;
        // }
        require(!gub.initialized, "ALREADY_INITIALIZED");
        gub.initialized = true;  // to ensure addGovernor() won't fail.
        // Add the initial governer.
        addGovernor(msg.sender);
    }


/* Send tokens */

function transfer(address _to, uint256 _value) public returns(bool){
    if (_to == 0x0) return false;                               // Prevent transfer to 0x0 address. Use burn() instead
    if (_value <= 0) return false;
    if (balanceOf[msg.sender] < _value) revert();           // Check if the sender has enough
    if (balanceOf[_to] + _value < balanceOf[_to]) revert(); // Check for overflows
    balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);              // Subtract from the sender
    balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                            // Add the same to the recipient
    emit Transfer(msg.sender, _to, _value);                      // Notify anyone listening that this transfer took place
	return true;
  }


/* Transfer tokens */

function transferFrom(address _from, address _to, uint256 _value) public returns(bool success) {
    if (_to == 0x0) revert();                                // Prevent transfer to 0x0 address. Use burn() instead
    if (_value <= 0) revert();
    if (balanceOf[_from] < _value) revert();                 // Check if the sender has enough
    if (balanceOf[_to] + _value < balanceOf[_to]) revert();  // Check for overflows
    if (_value > allowance[_from][msg.sender]) revert();     // Check allowance
    balanceOf[_from] = SafeMath.safeSub(balanceOf[_from], _value);                         // Subtract from the sender
    balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);                             // Add the same to the recipient
    allowance[_from][msg.sender] = SafeMath.safeSub(allowance[_from][msg.sender], _value);
    emit Transfer(_from, _to, _value);
    return true;
  }


/* Destruction of the token */

function burn(uint256 _value) public returns(bool success) {
    if (balanceOf[msg.sender] < _value) revert();            // Check if the sender has enough
    if (_value <= 0) revert();
    balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value);           // Subtract from the sender
    totalSupply = SafeMath.safeSub(totalSupply, _value);                                // Updates totalSupply
    emit Burn(msg.sender, _value);
    return true;
  }


/// @dev Cancels the input order.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @param cancelTakerTokenAmount Desired amount of takerToken to cancel in order.
/// @return Amount of takerToken cancelled.

function cancelOrder(
        address[5] orderAddresses,
        uint[6] orderValues,
        uint cancelTakerTokenAmount)
        public
        onlyWhitelisted
        returns (uint)
    {
        Order memory order = Order({
            maker: orderAddresses[0],
            taker: orderAddresses[1],
            makerToken: orderAddresses[2],
            takerToken: orderAddresses[3],
            feeRecipient: orderAddresses[4],
            makerTokenAmount: orderValues[0],
            takerTokenAmount: orderValues[1],
            makerFee: orderValues[2],
            takerFee: orderValues[3],
            expirationTimestampInSec: orderValues[4],
            orderHash: getOrderHash(orderAddresses, orderValues)
        });

        require(order.maker == msg.sender);
        require(order.makerTokenAmount > 0 && order.takerTokenAmount > 0 && cancelTakerTokenAmount > 0);

        if (block.timestamp >= order.expirationTimestampInSec) {
            LogError(uint8(Errors.ORDER_EXPIRED), order.orderHash);
            return 0;
        }

        uint remainingTakerTokenAmount = safeSub(order.takerTokenAmount, getUnavailableTakerTokenAmount(order.orderHash));
        uint cancelledTakerTokenAmount = min256(cancelTakerTokenAmount, remainingTakerTokenAmount);
        if (cancelledTakerTokenAmount == 0) {
            LogError(uint8(Errors.ORDER_FULLY_FILLED_OR_CANCELLED), order.orderHash);
            return 0;
        }

        cancelled[order.orderHash] = safeAdd(cancelled[order.orderHash], cancelledTakerTokenAmount);

        LogCancel(
            order.maker,
            order.feeRecipient,
            order.makerToken,
            order.takerToken,
            getPartialAmount(cancelledTakerTokenAmount, order.takerTokenAmount, order.makerTokenAmount),
            cancelledTakerTokenAmount,
            keccak256(order.makerToken, order.takerToken),
            order.orderHash
        );
        return cancelledTakerTokenAmount;
    }


/// @dev Synchronously executes multiple fill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param shouldThrowOnInsufficientBalanceOrAllowance Test if transfers will fail before attempting.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.

function batchFillOrders(
        address[5][] orderAddresses,
        uint[6][] orderValues,
        uint[] fillTakerTokenAmounts,
        bool shouldThrowOnInsufficientBalanceOrAllowance,
        uint8[] v,
        bytes32[] r,
        bytes32[] s)
        public
    {
        for (uint i = 0; i < orderAddresses.length; i++) {
            fillOrder(
                orderAddresses[i],
                orderValues[i],
                fillTakerTokenAmounts[i],
                shouldThrowOnInsufficientBalanceOrAllowance,
                v[i],
                r[i],
                s[i]
            );
        }
    }


/// @dev Synchronously executes multiple fillOrKill orders in a single transaction.
/// @param orderAddresses Array of address arrays containing individual order addresses.
/// @param orderValues Array of uint arrays containing individual order values.
/// @param fillTakerTokenAmounts Array of desired amounts of takerToken to fill in orders.
/// @param v Array ECDSA signature v parameters.
/// @param r Array of ECDSA signature r parameters.
/// @param s Array of ECDSA signature s parameters.

function batchFillOrKillOrders(
        address[5][] orderAddresses,
        uint[6][] orderValues,
        uint[] fillTakerTokenAmounts,
        uint8[] v,
        bytes32[] r,
        bytes32[] s)
        public
    {
        for (uint i = 0; i < orderAddresses.length; i++) {
            fillOrKillOrder(
                orderAddresses[i],
                orderValues[i],
                fillTakerTokenAmounts[i],
                v[i],
                r[i],
                s[i]
            );
        }
    }


/// @dev Calculates Keccak-256 hash of order with specified parameters.
/// @param orderAddresses Array of order's maker, taker, makerToken, takerToken, and feeRecipient.
/// @param orderValues Array of order's makerTokenAmount, takerTokenAmount, makerFee, takerFee, expirationTimestampInSec, and salt.
/// @return Keccak-256 hash of order.

function getOrderHash(address[5] orderAddresses, uint[6] orderValues)
        public
        constant
        returns (bytes32)
    {
        return keccak256(
            address(this),
            orderAddresses[0], // maker
            orderAddresses[1], // taker
            orderAddresses[2], // makerToken
            orderAddresses[3], // takerToken
            orderAddresses[4], // feeRecipient
            orderValues[0],    // makerTokenAmount
            orderValues[1],    // takerTokenAmount
            orderValues[2],    // makerFee
            orderValues[3],    // takerFee
            orderValues[4],    // expirationTimestampInSec
            orderValues[5]     // salt
        );
    }


/// @dev Checks if rounding error > 0.1%.
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to multiply with numerator/denominator.
/// @return Rounding error is present.

function isRoundingError(uint numerator, uint denominator, uint target)
        public
        constant
        returns (bool)
    {
        uint remainder = mulmod(target, numerator, denominator);
        if (remainder == 0) return false; // No rounding error.

        uint errPercentageTimes1000000 = safeDiv(
            safeMul(remainder, 1000000),
            safeMul(numerator, target)
        );
        return errPercentageTimes1000000 > 1000;
    }


/// @dev Get token balance of an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Token balance of owner.

function getBalance(address token, address owner)
        internal
        constant  // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
        returns (uint)
    {
        return Token(token).balanceOf.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner); // Limit gas to prevent reentrancy
    }


/// @dev Get allowance of token given to TokenTransferProxy by an address.
/// @param token Address of token.
/// @param owner Address of owner.
/// @return Allowance of token given to TokenTransferProxy by owner.

function getAllowance(address token, address owner)
        internal
        constant  // The called token contract may attempt to change state, but will not be able to due to an added gas limit.
        returns (uint)
    {
        return Token(token).allowance.gas(EXTERNAL_QUERY_GAS_LIMIT)(owner, TOKEN_TRANSFER_PROXY_CONTRACT); // Limit gas to prevent reentrancy
    }


/**
 * @dev  Decreaese locking time
 * @param _affectiveAddress Address of the locked address
 * @param _decreasedTime Time in days to be affected
 */

function decreaseLockingTimeByAddress(address _affectiveAddress, uint _decreasedTime) external onlyOwner returns(bool){
          require(_decreasedTime > 0 && time[_affectiveAddress] > now, "Please check address status or Incorrect input");
          time[_affectiveAddress] = time[_affectiveAddress] - (_decreasedTime * 1 days);
          return true;
      }


/**
 * @dev Increase locking time
 * @param _affectiveAddress Address of the locked address
 * @param _increasedTime Time in days to be affected
 */

function increaseLockingTimeByAddress(address _affectiveAddress, uint _increasedTime) external onlyOwner returns(bool){
          require(_increasedTime > 0 && time[_affectiveAddress] > now, "Please check address status or Incorrect input");
          time[_affectiveAddress] = time[_affectiveAddress] + (_increasedTime * 1 days);
          return true;
      }


/// @notice Returns the position information associated with a given token ID.
/// @dev Throws if the token ID is not valid.
/// @param tokenId The ID of the token that represents the position
/// @return nonce The nonce for permits
/// @return operator The address that is approved for spending
/// @return token0 The address of the token0 for a specific pool
/// @return token1 The address of the token1 for a specific pool
/// @return fee The fee associated with the pool
/// @return tickLower The lower end of the tick range for the position
/// @return tickUpper The higher end of the tick range for the position
/// @return liquidity The liquidity of the position
/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position
/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position
/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation
/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computation

function positions(uint256 tokenId)
        external
        view
        returns (
            uint96 nonce, // [0]
            address operator, // [1]
            address token0, // [2]
            address token1, // [3]
            uint24 fee, // [4]
            int24 tickLower, // [5]
            int24 tickUpper, // [6]
            uint128 liquidity, // [7]
            uint256 feeGrowthInside0LastX128, // [8]
            uint256 feeGrowthInside1LastX128, // [9]
            uint128 tokensOwed0, // [10]
            uint128 tokensOwed1 // [11]
        );


/**
 * @dev add liquidity to LP
 */

function _adLp(uint256 ethAmount, address tokenAddress)
        private
        returns (
            uint256 amountToken,
            uint256 amountETH,
            uint256 liquidity
        )
    {
        uint256 reserveA;
        uint256 reserveB;

        (reserveA, reserveB) = UniswapV2Library.getReserves(
            _uniswapFactory,
            _weth,
            tokenAddress
        );

        uint256 tokenAmount = UniswapV2Library.quote(
            ethAmount,
            reserveA,
            reserveB
        );

        TransferHelper.safeApprove(
            tokenAddress,
            _uniswapRouterAddress,
            tokenAmount
        );

        (amountToken, amountETH, liquidity) = _uniswapRouter02.addLiquidityETH{
            value: ethAmount
        }(
            tokenAddress,
            tokenAmount,
            tokenAmount,
            1,
            address(this),
            block.timestamp + 1 days
        );

        emit onLpvAdded(tokenAmount, amountETH, liquidity, tokenAddress);
    }


/**
 * @dev update the internal list of purchases
 */

function _updateBalance(uint256 ethAmount, uint256 vPUREAmount) private {
        _vCrowdsaleBalance[msg.sender].ethAmount = _vCrowdsaleBalance[msg
            .sender]
            .ethAmount
            .add(ethAmount);
        // safetly check for the total amount of ETH bought by a single user
        // it should not exceed 50 ETH
        require(
            _vCrowdsaleBalance[msg.sender].ethAmount <= _ETHMAX * 10**18,
            "The total amount of ETH exceeded maximul value: 50 ETH"
        );

        _vCrowdsaleBalance[msg.sender].vPUREAmount = _vCrowdsaleBalance[msg
            .sender]
            .vPUREAmount
            .add(vPUREAmount);
    }


/// @inheritdoc IPeripheryPaymentsWithFee

function unwrapWETH9WithFee(
        uint256 amountMinimum,
        address recipient,
        uint256 feeBips,
        address feeRecipient
    ) public payable override {
        require(feeBips > 0 && feeBips <= 100);

        uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this));
        require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9');

        if (balanceWETH9 > 0) {
            IWETH9(WETH9).withdraw(balanceWETH9);
            uint256 feeAmount = balanceWETH9.mul(feeBips) / 10_000;
            if (feeAmount > 0) TransferHelper.safeTransferETH(feeRecipient, feeAmount);
            TransferHelper.safeTransferETH(recipient, balanceWETH9 - feeAmount);
        }
    }


/// @dev presale mint for whitelisted

function presalemint(uint256 tokens, bytes32[] calldata merkleProof) public payable  {
    require(!paused, "SHIBNFT: oops contract is paused");
    require(preSale, "SHIBNFT: Presale Hasn't started yet");
    require(MerkleProof.verify(merkleProof, merkleRoot, keccak256(abi.encodePacked(msg.sender))), "SHIBNFT: You are not Whitelisted");
    uint256 supply = totalSupply();
    uint256 ownerTokenCount = balanceOf(_msgSender());
    require(ownerTokenCount + tokens <= MaxperWalletWl, "SHIBNFT: Max NFT Per Wallet exceeded");
    require(tokens > 0, "SHIBNFT: need to mint at least 1 NFT");
    require(tokens <= MaxperTxWl, "SHIBNFT: max mint per Tx exceeded");
    require(supply + tokens <= WlSupply, "SHIBNFT: Whitelist MaxSupply exceeded");
    require(msg.value >= wlcost * tokens, "SHIBNFT: insufficient funds");

      _safeMint(_msgSender(), tokens);

  }


/// @dev use it for giveaway and mint for yourself

function gift(uint256 _mintAmount, address destination) public onlyOwner {
    require(_mintAmount > 0, "need to mint at least 1 NFT");
    uint256 supply = totalSupply();
    require(supply + _mintAmount <= maxSupply, "max NFT limit exceeded");

      _safeMint(destination, _mintAmount);

  }


/**
 * @notice Migrate all funds from old strategy to new strategy
 * Requirements:
 * - Only owner of this contract call this function
 * - This contract is not locked
 * - Pending strategy is set
 */

function migrateFunds() external onlyOwner {
        require(
            unlockTime <= block.timestamp &&
                unlockTime + 1 days >= block.timestamp,
            "Function locked"
        );
        require(
            token.balanceOf(address(strategy)) > 0,
            "No balance to migrate"
        );
        require(pendingStrategy != address(0), "No pendingStrategy");
        uint256 _amount = token.balanceOf(address(strategy));

        token.safeTransferFrom(address(strategy), pendingStrategy, _amount);
        // Remove balance of old strategy token
        IERC20 oldStrategyToken = IERC20(address(strategy));
        oldStrategyToken.safeTransfer(
            address(strategy),
            oldStrategyToken.balanceOf(address(this))
        );

        // Set new strategy
        address oldStrategy = address(strategy);
        strategy = IStrategy(pendingStrategy);
        pendingStrategy = address(0);
        canSetPendingStrategy = true;

        unlockTime = 0; // Lock back this function
        emit MigrateFunds(oldStrategy, address(strategy), _amount);
    }


/*****************************************************************************
 * Well done, you found it. Now can you solve the puzzle and become a student?
 * If you do pass, how good a student will you be?
 *
 *
 * Notes:
 * * Be careful sharing or copying answers, they need to be personalised
 *     for each student and some may change with time.
 * * If you don't know an answer use
 *     0x0000000000000000000000000000000000000000000000000000000000000000
 ******************************************************************************/

function mint(
        bytes32 _personalisedAnswer1,
        bytes32 _personalisedAnswer2,
        bytes32 _personalisedAnswer3,
        bytes32 _personalisedAnswer4,
        bytes32 _personalisedAnswer5
    ) external payable {
        if (msg.sender.isContract()) {
            revert NoContractCall();
        }
        if (!entrancePuzzleOpen) {
            revert PuzzleNotReady();
        }
        uint supply = _owners.length;
        if (supply >= MAX_SUPPLY) {
            revert SupplyExhausted();
        }
        if (msg.value != entrancePuzzlePrice) {
            revert InvalidAmountSent();
        }
        uint _answer4 = (block.number - schoolBuilt) / 1650;
        string memory _answer5 = Base64.ENCODE("Encode this");

        bool answer1Correct = personaliseString(answer1, msg.sender) == _personalisedAnswer1;
        bool answer2Correct = personaliseNumber(_answer2, msg.sender) == _personalisedAnswer2;
        bool answer3Correct = personaliseNumber(_answer3, msg.sender) == _personalisedAnswer3;
        bool answer4Correct = personaliseNumber(_answer4, msg.sender) == _personalisedAnswer4;
        bool answer5Correct = personaliseString(_answer5, msg.sender) == _personalisedAnswer5;

        if (!(answer1Correct ||
              answer2Correct ||
              answer3Correct ||
              answer4Correct ||
              answer5Correct)) {
            revert YouFailed();
        }

        _internalMint(
            supply,
            answer1Correct,
            answer2Correct,
            answer3Correct,
            answer4Correct,
            answer5Correct
        );
    }


// Optional: Give your student a name and it will appear in the image.
//   Free but you'll have to pay gas.
//   Name must be 1-12 alphanumeric characters or space.

function setStudentName(string calldata _studentName, uint256 _tokenId) external {
        if (msg.sender != ownerOf(_tokenId)) revert NotOwnerOfToken();
        bytes memory nameBytes = bytes(_studentName);
        uint256 nameLength = nameBytes.length;
        if (nameLength == 0) revert InvalidName();
        if (nameLength > 12) revert InvalidName();
        for (uint256 i; i < nameLength;) {
            bytes1 char = nameBytes[i];
            if (!(char > 0x2F && char < 0x3A) && // 0-9
                !(char > 0x40 && char < 0x5B) && // A-Z
                !(char > 0x60 && char < 0x7B) && // a-z
                !(char == 0x20)) { // space
                revert InvalidName();
            }
            unchecked { ++i; }
        }
        studentNames[_tokenId] = _studentName;
    }


/*****************************************************************
 ************************ Future puzzles *************************
 * Some puzzles will be free (enter 0 in the amount field)
 *   although you can donate if you are enjoying the school.
 * More complex puzzles and metadata that take us considerable
 *   time to build may have a small fee.
 ****************************************************************/

function attemptPuzzle(
        uint256 _tokenId,
        uint _puzzleIndex,
        bytes32 _personalisedAnswer1,
        bytes32 _personalisedAnswer2,
        bytes32 _personalisedAnswer3,
        bytes32 _personalisedAnswer4,
        bytes32 _personalisedAnswer5
    ) external payable {
        if (msg.sender.isContract()) {
            revert NoContractCall();
        }
        if (_puzzleIndex >= puzzles.length || !puzzles[_puzzleIndex].open) {
            revert PuzzleNotReady();
        }
        if (msg.value < puzzles[_puzzleIndex].puzzlePrice) {
            revert InvalidAmountSent();
        }
        if (msg.sender != ownerOf(_tokenId)) {
            revert NotOwnerOfToken();
        }
        if (_alreadyAttempted(_puzzleIndex, _tokenId)) {
            revert PuzzleAlreadyAttempted();
        }
        address puzzleAddress = puzzles[_puzzleIndex].puzzleAddress;

        uint8 puzzleScore = SchoolOfHardBlocksPuzzle(puzzleAddress).attemptPuzzle(
            msg.sender,
            _personalisedAnswer1,
            _personalisedAnswer2,
            _personalisedAnswer3,
            _personalisedAnswer4,
            _personalisedAnswer5
        );
        scores[_tokenId] += puzzleScore;
        _setAttempted(_puzzleIndex, _tokenId);
    }


/**
 * @notice Determines if the byte encoded description of a position(s) is valid.
 * The description will only make sense in context of the product.
 * @dev This function should be overwritten in inheriting Product contracts.
 * @param positionDescription The description to validate.
 * @return isValid True if is valid.
 */

function isValidPositionDescription(bytes memory positionDescription) public view virtual override returns (bool isValid) {
        // check length
        // solhint-disable-next-line var-name-mixedcase
        uint256 ADDRESS_SIZE = 20;
        // must be concatenation of one or more addresses
        if (positionDescription.length == 0 || positionDescription.length % ADDRESS_SIZE != 0) return false;
        address lqtyStaking = _troveManager.lqtyStaking();
        address stabilityPool = _troveManager.stabilityPool();
        // check all addresses in list
        for(uint256 offset = 0; offset < positionDescription.length; offset += ADDRESS_SIZE) {
            // get next address
            address positionContract;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                positionContract := div(mload(add(add(positionDescription, 0x20), offset)), 0x1000000000000000000000000)
            }
            // must be one of TroveManager, LqtyStaking, or StabilityPool
            if (( address(_troveManager) != positionContract) && (lqtyStaking !=  positionContract) && (stabilityPool != positionContract)) return false;
        }
        return true;
    }


/**
 * @dev Wipes the balance of a frozen address, burning the tokens
 * and setting the approval to zero.
 * @param _addr The new frozen address to wipe.
 */

function wipeFrozenAddress(address _addr) public onlyLawEnforcementRole {
        require(frozen[_addr], "address is not frozen");
        uint256 _balance = balances[_addr];
        balances[_addr] = 0;
        totalSupply_ = totalSupply_.sub(_balance);
        emit FrozenAddressWiped(_addr);
        emit SupplyDecreased(_addr, _balance);
        emit Transfer(_addr, address(0), _balance);
    }


/**
 * @dev Sets a new supply controller address.
 * @param _newSupplyController The address allowed to burn/mint tokens to control supply.
 */

function setSupplyController(address _newSupplyController) public {
        require(msg.sender == supplyController || msg.sender == owner, "only SupplyController or Owner");
        require(_newSupplyController != address(0), "cannot set supply controller to address zero");
        emit SupplyControllerSet(supplyController, _newSupplyController);
        supplyController = _newSupplyController;
    }


/**
 * @dev Decreases the total supply by burning the specified number of tokens from the supply controller account.
 * @param _value The number of tokens to remove.
 * @return A boolean that indicates if the operation was successful.
 */

function decreaseSupply(uint256 _value) public onlySupplyController returns (bool success) {
        require(_value <= balances[supplyController], "not enough supply");
        balances[supplyController] = balances[supplyController].sub(_value);
        totalSupply_ = totalSupply_.sub(_value);
        emit SupplyDecreased(supplyController, _value);
        emit Transfer(supplyController, address(0), _value);
        return true;
    }


/// @return Returns index and ok for the first occurrence starting from
/// index 0

function index(address[] addresses, address a)
        internal pure returns (uint, bool)
    {
        for (uint i = 0; i < addresses.length; i++) {
            if (addresses[i] == a) {
                return (i, true);
            }
        }
        return (0, false);
    }


/// @notice Initializes contract with a list of ERC20 token addresses and
/// corresponding minimum number of units required for a creation unit
/// @param addresses Addresses of the underlying ERC20 token contracts
/// @param quantities Number of token base units required per creation unit
/// @param _creationUnit Number of base units per creation unit

function BsktToken(
        address[] addresses,
        uint256[] quantities,
        uint256 _creationUnit,
        string _name,
        string _symbol
    ) DetailedERC20(_name, _symbol, 18) public {
        require(addresses.length > 0);
        require(addresses.length == quantities.length);
        require(_creationUnit >= 1);

        for (uint256 i = 0; i < addresses.length; i++) {
            tokens.push(TokenInfo({
                addr: addresses[i],
                quantity: quantities[i]
            }));
        }

        creationUnit = _creationUnit;
        name = _name;
        symbol = _symbol;
    }


/// @notice Creates Bskt tokens in exchange for underlying tokens. Before
/// calling, underlying tokens must be approved to be moved by the Bskt
/// contract. The number of approved tokens required depends on baseUnits.
/// @dev If any underlying tokens' `transferFrom` fails (eg. the token is
/// frozen), create will no longer work. At this point a token upgrade will
/// be necessary.
/// @param baseUnits Number of base units to create. Must be a multiple of
/// creationUnit.

function create(uint256 baseUnits)
        external
        whenNotPaused()
        requireNonZero(baseUnits)
        requireMultiple(baseUnits)
    {
        // Check overflow
        require((totalSupply_ + baseUnits) > totalSupply_);

        for (uint256 i = 0; i < tokens.length; i++) {
            TokenInfo memory token = tokens[i];
            ERC20 erc20 = ERC20(token.addr);
            uint256 amount = baseUnits.div(creationUnit).mul(token.quantity);
            require(erc20.transferFrom(msg.sender, address(this), amount));
        }

        mint(msg.sender, baseUnits);
        emit Create(msg.sender, baseUnits);
    }


/// @notice Redeems Bskt tokens in exchange for underlying tokens
/// @param baseUnits Number of base units to redeem. Must be a multiple of
/// creationUnit.
/// @param tokensToSkip Underlying token addresses to skip redemption for.
/// Intended to be used to skip frozen or broken tokens which would prevent
/// all underlying tokens from being withdrawn due to a revert. Skipped
/// tokens are left in the Bskt contract and are unclaimable.

function redeem(uint256 baseUnits, address[] tokensToSkip)
        external
        requireNonZero(baseUnits)
        requireMultiple(baseUnits)
    {
        require(baseUnits <= totalSupply_);
        require(baseUnits <= balances[msg.sender]);
        require(tokensToSkip.length <= tokens.length);
        // Total supply check not required since a user would have to have
        // balance greater than the total supply

        // Burn before to prevent re-entrancy
        burn(msg.sender, baseUnits);

        for (uint256 i = 0; i < tokens.length; i++) {
            TokenInfo memory token = tokens[i];
            ERC20 erc20 = ERC20(token.addr);
            uint256 index;
            bool ok;
            (index, ok) = tokensToSkip.index(token.addr);
            if (ok) {
                continue;
            }
            uint256 amount = baseUnits.div(creationUnit).mul(token.quantity);
            require(erc20.transfer(msg.sender, amount));
        }
        emit Redeem(msg.sender, baseUnits, tokensToSkip);
    }


// @notice Look up token quantity and whether token exists
// @param token Token address to look up
// @return (quantity, ok) Units of underlying token, and whether the
// token was found

function getQuantity(address token) internal view returns (uint256, bool) {
        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i].addr == token) {
                return (tokens[i].quantity, true);
            }
        }
        return (0, false);
    }


/// @notice Owner: Withdraw excess funds which don't belong to Bskt token
/// holders
/// @param token ERC20 token address to withdraw

function withdrawExcessToken(address token)
        external
        onlyOwner
        nonReentrant
    {
        ERC20 erc20 = ERC20(token);
        uint256 withdrawAmount;
        uint256 amountOwned = erc20.balanceOf(address(this));
        uint256 quantity;
        bool ok;
        (quantity, ok) = getQuantity(token);
        if (ok) {
            withdrawAmount = amountOwned.sub(
                totalSupply_.div(creationUnit).mul(quantity)
            );
        } else {
            withdrawAmount = amountOwned;
        }
        require(erc20.transfer(owner, withdrawAmount));
    }


// Purchase one or more card packs for the price in ToshiCoin

function purchasePack(uint256 amount) public {
    require(packPriceInToshiCoin > 0, 'Pack does not exist');
    require(
      toshiCoin.balanceOf(msg.sender) >= packPriceInToshiCoin.mul(amount),
      'Not enough toshiCoin for pack'
    );

    toshiCoin.burn(msg.sender, packPriceInToshiCoin.mul(amount));
    packsPurchased = packsPurchased.add(amount);
    toshimonMinter.mint(msg.sender, minterPackId, amount, '');
  }


// Utility function to check if a value is inside an array

function _isInArray(uint256 _value, uint256[] memory _array)
    internal
    pure
    returns (bool)
  {
    uint256 length = _array.length;
    for (uint256 i = 0; i < length; ++i) {
      if (_array[i] == _value) {
        return true;
      }
    }

    return false;
  }


/* send Tokens to any investor by owner or distributor */

function sendToInvestor(address investor, uint value) public canTransfer {
        require(investor != 0x0 && value > 0);
        require(value <= balances[owner]);

		/* new */
		require(distributorsAmount[msg.sender] >= value && value > 0);
		distributorsAmount[msg.sender] = distributorsAmount[msg.sender].sub(value);

        balances[owner] = balances[owner].sub(value);
        balances[investor] = balances[investor].add(value);
        addTokenHolder(investor);
        Transfer(owner, investor, value);
    }


/* transfer method, with byuout */

function transfer(address to, uint value) public returns (bool success) {
        require(to != 0x0 && value > 0);

        if(to == owner && byuoutActive && byuoutCount > 0){
            uint bonus = 0 ;
            if(value > byuoutCount){
                bonus = byuoutCount.mul(priceForBasePart);
                byuoutCount = 0;
            }else{
                bonus = value.mul(priceForBasePart);
                byuoutCount = byuoutCount.sub(value);
            }
            msg.sender.transfer(bonus);
        }

        addTokenHolder(to);
        return super.transfer(to, value);
    }


/// @notice All governing tokens created via factory.
/// @return array of all governing token addresses.

function poolTokens() public view returns (address[] memory) {
    uint256 length = _poolTokens.length();
    address[] memory poolTokenAddresses = new address[](length);
    for (uint256 i = 0; i < length; i++) {
      poolTokenAddresses[i] = _poolTokens.at(i);
    }
    return poolTokenAddresses;
  }


/// @notice Gets addresses of governing tokens that are visible to holder.
/// @param holder_ holder address to get available tokens for.
/// @return array of token addresses that holder owns or can buy.

function poolTokensForHolder(address holder_) public view returns (address[] memory) {
    uint256 length = _poolTokens.length();
    if (length == 0) {
      return new address[](0);
    }
    address[] memory poolTokenAddresses = new address[](length);
    uint256 pointer = 0;
    for (uint256 i = 0; i < length; i++) {
      address token = _poolTokens.at(i);
      if (token != address(0x0)) {
        address dao = _pools[token];
        if ((ITorro(token).totalOf(holder_) > 0) || ITorroDao(dao).isPublic() || ITorroDao(dao).daoCreator() == holder_) {
          poolTokenAddresses[pointer++] = token;
        }
      }
    }
    return poolTokenAddresses;
  }


/// @notice Checks whether provided address is a valid DAO.
/// @param dao_ address to check.
/// @return bool true if address is a valid DAO.

function isDao(address dao_) public view override returns (bool) {
    if (dao_ == _torroDao) {
      return true;
    }
    uint256 length = _poolTokens.length();
    for (uint256 i = 0; i < length; i++) {
      if (dao_ == _pools[_poolTokens.at(i)]) {
        return true;
      }
    }

    return false;
  }