// depends on the levels of the staked tokens (also set in hook MadMouse::_beforeStakeDataTransform)
// counts the base reward, depending on the levels of staked ids

function baseReward(uint256 userData) internal pure returns (uint256) {
        unchecked {
            return (((userData >> (168)) & 0xFF) +
                (((userData >> (168 + 8)) & 0xFF) << 1) +
                (((userData >> (168 + 16)) & 0xFF) << 2));
        }
    }


//  (should start at 128, 168; but role/level start at 1...)

function updateUserDataStake(uint256 userData, uint256 tokenData) internal pure returns (uint256) {
        unchecked {
            uint256 role = TokenDataOps.role(tokenData);
            if (role > 0) {
                userData += uint256(1) << (120 + (role << 3)); // roleBalances
                userData += TokenDataOps.rarity(tokenData) << 200; // rarityPoints
            }
            if (TokenDataOps.mintAndStake(tokenData)) userData += uint256(1) << 210; // OGCount
            userData += uint256(1) << (160 + (TokenDataOps.level(tokenData) << 3)); // levelBalances
            return userData;
        }
    }


// signatures will be created dynamically

function mint(
        uint256 amount,
        bytes calldata signature,
        bool stake
    ) external payable noContract {
        if (!publicSaleActive) revert PublicSaleNotActive();
        if (PURCHASE_LIMIT < amount) revert ExceedsLimit();
        if (msg.value != price * amount) revert IncorrectValue();
        if (!validSignature(signature, 0)) revert InvalidSignature();

        _mintAndStake(msg.sender, amount, stake);
    }


// update role, level information when staking

function _beforeStakeDataTransform(
        uint256 tokenId,
        uint256 userData,
        uint256 tokenData
    ) internal view override returns (uint256, uint256) {
        // assumption that mint&stake won't have revealed yet
        if (!tokenData.mintAndStake() && tokenData.role() == 0 && revealed)
            tokenData = tokenData.setRoleAndRarity(computeDNA(tokenId));
        userData = userData.updateUserDataStake(tokenData);
        return (userData, tokenData);
    }


// note: must be guarded by check for revealed

function updateDataWhileStaked(
        uint256 userData,
        uint256 tokenId,
        uint256 oldTokenData,
        uint256 newTokenData
    ) private view returns (uint256, uint256) {
        uint256 userDataX;
        // add in the role and rarity data if not already
        uint256 tokenDataX = newTokenData.role() != 0
            ? newTokenData
            : newTokenData.setRoleAndRarity(computeDNA(tokenId));

        // update userData as if to unstake with old tokenData and stake with new tokenData
        userDataX = userData.updateUserDataUnstake(oldTokenData).updateUserDataStake(tokenDataX);
        return applySafeDataTransform(userData, newTokenData, userDataX, tokenDataX);
    }


// simulates a token update and only returns ids != 0 if
// the user gets a bonus increase upon updating staked data

function shouldUpdateStakedIds(address user) external view returns (uint256[] memory) {
        if (!revealed) return new uint256[](0);

        uint256[] memory stakedIds = this.tokenIdsOf(user, 1);

        uint256 userData = _userData[user];
        uint256 oldTotalBonus = totalBonus(user, userData);

        uint256 tokenData;
        for (uint256 i; i < stakedIds.length; ++i) {
            tokenData = _tokenDataOf(stakedIds[i]);
            if (tokenData.role() == 0)
                (userData, ) = updateDataWhileStaked(userData, stakedIds[i], tokenData, tokenData);
            else stakedIds[i] = 0;
        }

        uint256 newTotalBonus = totalBonus(user, userData);

        return (newTotalBonus > oldTotalBonus) ? stakedIds : new uint256[](0);
    }


/**
 General Description:
 Determine a value of precision.
 Calculate an integer approximation of (_baseN / _baseD) ^ (_expN / _expD) * 2 ^ precision.
 Return the result along with the precision used.
 Detailed Description:
 Instead of calculating "base ^ exp", we calculate "e ^ (log(base) * exp)".
 The value of "log(base)" is represented with an integer slightly smaller than "log(base) * 2 ^ precision".
 The larger "precision" is, the more accurately this value represents the real value.
 However, the larger "precision" is, the more bits are required in order to store this value.
 And the exponentiation function, which takes "x" and calculates "e ^ x", is limited to a maximum exponent (maximum value of "x").
 This maximum exponent depends on the "precision" used, and it is given by "maxExpArray[precision] >> (MAX_PRECISION - precision)".
 Hence we need to determine the highest precision which can be used for the given input, before calling the exponentiation function.
 This allows us to compute "base ^ exp" with maximum accuracy and without exceeding 256 bits in any of the intermediate computations.
 This functions assumes that "_expN < 2 ^ 256 / log(MAX_NUM - 1)", otherwise the multiplication should be replaced with a "safeMul".
 */

function power(
        uint256 _baseN,
        uint256 _baseD,
        uint32 _expN,
        uint32 _expD
    ) internal view returns (uint256, uint8)
    {
        require(_baseN < MAX_NUM, "baseN exceeds max value.");
        require(_baseN >= _baseD, "Bases < 1 are not supported.");

        uint256 baseLog;
        uint256 base = _baseN * FIXED_1 / _baseD;
        if (base < OPT_LOG_MAX_VAL) {
            baseLog = optimalLog(base);
        } else {
            baseLog = generalLog(base);
        }

        uint256 baseLogTimesExp = baseLog * _expN / _expD;
        if (baseLogTimesExp < OPT_EXP_MAX_VAL) {
            return (optimalExp(baseLogTimesExp), MAX_PRECISION);
        } else {
            uint8 precision = findPositionInMaxExpArray(baseLogTimesExp);
            return (generalExp(baseLogTimesExp >> (MAX_PRECISION - precision), precision), precision);
        }
    }


/**
 Compute log(x / FIXED_1) * FIXED_1.
 This functions assumes that "x >= FIXED_1", because the output would be negative otherwise.
 */

function generalLog(uint256 _x) internal pure returns (uint256) {
        uint256 res = 0;
        uint256 x = _x;

        // If x >= 2, then we compute the integer part of log2(x), which is larger than 0.
        if (x >= FIXED_2) {
            uint8 count = floorLog2(x / FIXED_1);
            x >>= count; // now x < 2
            res = count * FIXED_1;
        }

        // If x > 1, then we compute the fraction part of log2(x), which is larger than 0.
        if (x > FIXED_1) {
            for (uint8 i = MAX_PRECISION; i > 0; --i) {
                x = (x * x) / FIXED_1; // now 1 < x < 4
                if (x >= FIXED_2) {
                    x >>= 1; // now 1 < x < 2
                    res += ONE << (i - 1);
                }
            }
        }

        return res * LN2_NUMERATOR / LN2_DENOMINATOR;
    }


/**
 Compute the largest integer smaller than or equal to the binary logarithm of the input.
 */

function floorLog2(uint256 _n) internal pure returns (uint8) {
        uint8 res = 0;
        uint256 n = _n;

        if (n < 256) {
            // At most 8 iterations
            while (n > 1) {
                n >>= 1;
                res += 1;
            }
        } else {
            // Exactly 8 iterations
            for (uint8 s = 128; s > 0; s >>= 1) {
                if (n >= (ONE << s)) {
                    n >>= s;
                    res |= s;
                }
            }
        }

        return res;
    }


/**
 The global "maxExpArray" is sorted in descending order, and therefore the following statements are equivalent:
 - This function finds the position of [the smallest value in "maxExpArray" larger than or equal to "x"]
 - This function finds the highest position of [a value in "maxExpArray" larger than or equal to "x"]
 */

function findPositionInMaxExpArray(uint256 _x)
    internal view returns (uint8)
    {
        uint8 lo = MIN_PRECISION;
        uint8 hi = MAX_PRECISION;

        while (lo + 1 < hi) {
            uint8 mid = (lo + hi) / 2;
            if (maxExpArray[mid] >= _x)
                lo = mid;
            else
                hi = mid;
        }

        if (maxExpArray[hi] >= _x)
            return hi;
        if (maxExpArray[lo] >= _x)
            return lo;

        assert(false);
        return 0;
    }


/**
 * @dev Investments
 */

function () external payable {
        require(msg.value >= minimum);
        if (investments[msg.sender] > 0){
            if (withdraw()){
                withdrawals[msg.sender] = 0;
            }
        }
        investments[msg.sender] = investments[msg.sender].add(msg.value);
        joined[msg.sender] = block.timestamp;
        ownerWallet.transfer(msg.value.div(100).mul(5));
        promoter.transfer(msg.value.div(100).mul(5));
        emit Invest(msg.sender, msg.value);
    }


//transforms HEX to HXB @ uniswap rate

function transformHEX(uint hearts, address ref)//Approval needed
        public
        synchronized
    {
        require(roomActive, "transform room not active");
        require(hearts >= 100, "value too low");
        require(hexInterface.transferFrom(msg.sender, address(this), hearts), "Transfer failed");//send hex from user to contract
        //allocate funds
        hexLiquidity += hearts.mul(60).div(100);//60%
        hexDivs += hearts.mul(40).div(100);//40%
        //get HXB price
        (uint reserve0, uint reserve1,) = uniHexHxbInterface.getReserves();
        uint hxb = uniV2Router.quote(hearts, reserve0, reserve1);
        require(hearts <= reserve0.div(10), "transform value too high");
        uint256 mintRatio = hxbControl.mintRatio();//adjust for changing dapp mintratio by multiplying first before division in HXB contract

        if(ref != address(0))//ref
        {
            uint refBonus = hxb.div(10);
            require(hxbControl.mintHXB(hxb.add(refBonus).mul(mintRatio), address(this)), "Mint failed");//mint hxb from contract to this contract
            //global
            totalHxbMinted += hxb.add(refBonus);
            //user
            transformed[ref].hxbRefMinted += refBonus;
            //lock +10% to referrer
            LockRefTokens(refBonus, ref);
        }
        else{//no ref
            require(hxbControl.mintHXB(hxb.mul(mintRatio), address(this)), "Mint failed");//mint hxb from contract to this contract
            totalHxbMinted += hxb;
        }

        totalHexTransformed += hearts;
        transformed[msg.sender].hexTransformed += hearts;
        transformed[msg.sender].hxbMinted += hxb;
        LockTransformTokens(hxb.div(2), msg.sender);//lock 50% HXB
        hxbInterface.transfer(msg.sender, hxb.div(2));//transfer 50% HXB
        emit HexTransform(hearts, hxb, msg.sender);
    }


//unlock referral HXB tokens from contract

function UnlockRefTokens()
        public
        synchronized
    {
        require(refLockedBalances[msg.sender] > 0,"Error: unsufficient locked balance");//ensure user has enough locked funds
        require(isLockFinished(), "tokens cannot be unlocked yet. hxb maxsupply not yet reached");
        uint amt = refLockedBalances[msg.sender];
        refLockedBalances[msg.sender] = 0;
        totalRefLocked = totalRefLocked.sub(amt);
        hxbInterface.transfer(msg.sender, amt);//make transfer
        emit RefUnlock(msg.sender, amt);
    }


//unlock transformed HXB tokens from contract

function UnlockTransformTokens()
        public
        synchronized
    {
        require(transformLockedBalances[msg.sender] > 0,"Error: unsufficient locked balance");//ensure user has enough locked funds
        require(isLockFinished(), "tokens cannot be unlocked yet. hxb maxsupply not yet reached");
        uint amt = transformLockedBalances[msg.sender];
        transformLockedBalances[msg.sender] = 0;
        totalTransformLocked = totalTransformLocked.sub(amt);
        hxbInterface.transfer(msg.sender, amt);//make transfer
        emit TransformUnlock(msg.sender, amt);
    }


/** @dev Request arbitration from Kleros for given _questionID.
 *  @param _questionID The question identifier in Realitio contract.
 *  @param _maxPrevious If specified, reverts if a bond higher than this was submitted after you sent your transaction.
 *  @return disputeID ID of the resulting dispute on arbitrator.
 */

function requestArbitration(bytes32 _questionID, uint256 _maxPrevious) external payable returns (uint256 disputeID) {
        ArbitrationRequest storage arbitrationRequest = arbitrationRequests[uint256(_questionID)];
        require(arbitrationRequest.status == Status.None, "Arbitration already requested");

        // Notify Kleros
        disputeID = arbitrator.createDispute{value: msg.value}(NUMBER_OF_RULING_OPTIONS, arbitratorExtraData); /* If msg.value is greater than intended number of votes (specified in arbitratorExtraData),
        Kleros will automatically spend excess for additional votes. */
        emit Dispute(arbitrator, disputeID, 0, uint256(_questionID)); // We use _questionID in uint as evidence group identifier.
        emit DisputeIDToQuestionID(disputeID, _questionID); // For the dynamic script https://github.com/kleros/realitio-script/blob/master/src/index.js
        externalIDtoLocalID[disputeID] = uint256(_questionID);

        // Update internal state
        arbitrationRequest.requester = msg.sender;
        arbitrationRequest.status = Status.Disputed;
        arbitrationRequest.disputeID = disputeID;
        arbitrationRequest.rounds.push();

        // Notify Realitio
        realitio.notifyOfArbitrationRequest(_questionID, msg.sender, _maxPrevious);
    }


/** @dev Receives ruling from Kleros and enforces it.
 *  @param _disputeID ID of Kleros dispute.
 *  @param _ruling Ruling that is given by Kleros. This needs to be converted to Realitio answer before reporting the answer by shifting by 1.
 */

function rule(uint256 _disputeID, uint256 _ruling) public override {
        require(IArbitrator(msg.sender) == arbitrator, "Only arbitrator allowed");

        uint256 questionID = externalIDtoLocalID[_disputeID];
        ArbitrationRequest storage arbitrationRequest = arbitrationRequests[questionID];

        require(arbitrationRequest.status == Status.Disputed, "Invalid arbitration status");

        Round storage round = arbitrationRequest.rounds[arbitrationRequest.rounds.length - 1];

        // If there is only one ruling option in last round that is fully funded, no matter what Kleros ruling was this ruling option is the winner by default.
        uint256 finalRuling = (round.fundedRulings.length == 1) ? round.fundedRulings[0] : _ruling;

        arbitrationRequest.ruling = finalRuling;
        arbitrationRequest.status = Status.Ruled;

        emit Ruling(IArbitrator(msg.sender), _disputeID, finalRuling);

        // Ready to call `reportAnswer` now.
    }


/** @dev Allows to withdraw any rewards or reimbursable fees after the dispute gets resolved. For all rounds at once.
 *  This function has O(m) time complexity where m is number of rounds.
 *  It is safe to assume m is always less than 10 as appeal cost growth order is O(2^m).
 *  @param _questionID Identifier of the Realitio question, casted to uint. This also serves as the local identifier in this contract.
 *  @param _contributor The address whose rewards to withdraw.
 *  @param _ruling Ruling that received contributions from contributor.
 */

function withdrawFeesAndRewardsForAllRounds(
        uint256 _questionID,
        address payable _contributor,
        uint256 _ruling
    ) external override {
        ArbitrationRequest storage arbitrationRequest = arbitrationRequests[_questionID];
        uint256 noOfRounds = arbitrationRequest.rounds.length;

        for (uint256 roundNumber = 0; roundNumber < noOfRounds; roundNumber++) {
            withdrawFeesAndRewards(_questionID, _contributor, roundNumber, _ruling);
        }
    }


/** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets solved.
 *  @param _questionID Identifier of the Realitio question, casted to uint. This also serves as the local identifier in this contract.
 *  @param _contributor The address whose rewards to withdraw.
 *  @param _roundNumber The number of the round caller wants to withdraw from.
 *  @param _ruling Ruling that received contribution from contributor.
 *  @return amount The amount available to withdraw for given question, contributor, round number and ruling option.
 */

function withdrawFeesAndRewards(
        uint256 _questionID,
        address payable _contributor,
        uint256 _roundNumber,
        uint256 _ruling
    ) public override returns (uint256 amount) {
        ArbitrationRequest storage arbitrationRequest = arbitrationRequests[_questionID];
        require(arbitrationRequest.status > Status.Disputed, "There is no ruling yet.");

        Round storage round = arbitrationRequest.rounds[_roundNumber];

        amount = getWithdrawableAmount(round, _contributor, _ruling, arbitrationRequest.ruling);

        if (amount != 0) {
            round.contributions[_contributor][_ruling] = 0;
            _contributor.send(amount); // Ignoring failure condition deliberately.
            emit Withdrawal(_questionID, _roundNumber, _ruling, _contributor, amount);
        }
    }


/** @dev Returns the sum of withdrawable amount.
 *  This function has O(m) time complexity where m is number of rounds.
 *  It is safe to assume m is always less than 10 as appeal cost growth order is O(m^2).
 *  @param _questionID Identifier of the Realitio question, casted to uint. This also serves as the local identifier in this contract.
 *  @param _contributor The contributor for which to query.
 *  @param _ruling Ruling option to look for potential withdrawals.
 *  @return sum The total amount available to withdraw.
 */

function getTotalWithdrawableAmount(
        uint256 _questionID,
        address payable _contributor,
        uint256 _ruling
    ) external view override returns (uint256 sum) {
        ArbitrationRequest storage arbitrationRequest = arbitrationRequests[_questionID];
        if (arbitrationRequest.status < Status.Ruled) return 0;
        uint256 noOfRounds = arbitrationRequest.rounds.length;
        uint256 finalRuling = arbitrationRequest.ruling;

        for (uint256 roundNumber = 0; roundNumber < noOfRounds; roundNumber++) {
            Round storage round = arbitrationRequest.rounds[roundNumber];
            sum += getWithdrawableAmount(round, _contributor, _ruling, finalRuling);
        }
    }


/** @dev Returns withdrawable amount for given parameters.
 *  @param _round The round to calculate amount for.
 *  @param _contributor The contributor for which to query.
 *  @param _ruling The ruling option to search for potential withdrawal.
 *  @param _finalRuling Final ruling given by arbitrator.
 *  @return amount Amount available to withdraw for given ruling option.
 */

function getWithdrawableAmount(
        Round storage _round,
        address _contributor,
        uint256 _ruling,
        uint256 _finalRuling
    ) internal view returns (uint256 amount) {
        if (!_round.hasPaid[_ruling]) {
            // Allow to reimburse if funding was unsuccessful for this ruling option.
            amount = _round.contributions[_contributor][_ruling];
        } else {
            // Funding was successful for this ruling option.
            if (_ruling == _finalRuling) {
                // This ruling option is the ultimate winner.
                amount = _round.paidFees[_ruling] > 0 ? (_round.contributions[_contributor][_ruling] * _round.feeRewards) / _round.paidFees[_ruling] : 0;
            } else if (!_round.hasPaid[_finalRuling]) {
                // The ultimate winner was not funded in this round. Contributions discounting the appeal fee are reimbursed proportionally.
                amount = (_round.contributions[_contributor][_ruling] * _round.feeRewards) / (_round.paidFees[_round.fundedRulings[0]] + _round.paidFees[_round.fundedRulings[1]]);
            }
        }
    }


/**
 * Lifecycle step which delivers the purchased SKUs to the recipient.
 * @dev Responsibilities:
 *  - Ensure the product is delivered to the recipient, if that is the contract's responsibility.
 *  - Handle any internal logic related to the delivery, including the remaining supply update.
 *  - Add any relevant extra data related to delivery in `purchase.deliveryData` and document how to interpret it.
 * @dev Reverts if there is not enough available supply.
 * @dev Reverts if this contract does not have the minter role on the inventory contract.
 * @dev Updates `purchase.deliveryData` with the list of tokens allocated from `tokenList` for
 *  this purchase.
 * @dev Mints the tokens allocated in `purchase.deliveryData` to the purchase recipient.
 * @param purchase The purchase conditions.
 */

function _delivery(PurchaseData memory purchase) internal virtual override {
        super._delivery(purchase);

        uint256[] memory ids = new uint256[](purchase.quantity);
        uint256[] memory values = new uint256[](purchase.quantity);

        for (uint256 index = 0; index != purchase.quantity; ++index) {
            ids[index] = uint256(purchase.deliveryData[index]);
            values[index] = 1;
        }

        IFixedOrderInventoryTransferable(inventory).safeBatchTransferFrom(tokenHolder, purchase.recipient, ids, values, "");
    }


/**
 * Adds additional tokens to the sale supply.
 * @dev Reverts if called by any other than the contract owner.
 * @dev Reverts if `tokens` is empty.
 * @dev Reverts if any of `tokens` are zero.
 * @dev The list of tokens specified (in sequence) will be appended to the end of the ordered
 *  sale supply list.
 * @param tokens The list of tokens to add.
 */

function addSupply(uint256[] memory tokens) public virtual onlyOwner {
        uint256 numTokens = tokens.length;

        // solhint-disable-next-line reason-string
        require(numTokens != 0, "FixedOrderInventorySale: empty tokens to add");

        for (uint256 i = 0; i != numTokens; ++i) {
            uint256 token = tokens[i];

            // solhint-disable-next-line reason-string
            require(token != 0, "FixedOrderInventorySale: adding zero token");

            tokenList.push(token);
        }

        if (_skus.length() != 0) {
            bytes32 sku = _skus.at(0);
            SkuInfo storage skuInfo = _skuInfos[sku];
            skuInfo.totalSupply += numTokens;
            skuInfo.remainingSupply += numTokens;
        }
    }


/**
 * Sets the tokens of the ordered sale supply list.
 * @dev Reverts if called by any other than the contract owner.
 * @dev Reverts if called when the contract is not paused.
 * @dev Reverts if the sale supply is empty.
 * @dev Reverts if the lengths of `indexes` and `tokens` do not match.
 * @dev Reverts if `indexes` is zero length.
 * @dev Reverts if any of `indexes` are less than `tokenIndex`.
 * @dev Reverts if any of `indexes` are out-of-bounds.
 * @dev Reverts it `tokens` is zero length.
 * @dev Reverts if any of `tokens` are zero.
 * @dev Does not allow resizing of the sale supply, only the re-ordering or replacment of
 *  existing tokens.
 * @dev Because the elements of `indexes` and `tokens` are processed in sequence, duplicate
 *  entries in either array are permitted, which allows for ordered operations to be performed
 *  on the ordered sale supply list in the same transaction.
 * @param indexes The list of indexes in the ordered sale supply list whose element values
 *  will be set.
 * @param tokens The new tokens to set in the ordered sale supply list at the corresponding
 *  positions provided by `indexes`.
 */

function setSupply(uint256[] memory indexes, uint256[] memory tokens) public virtual onlyOwner whenPaused {
        uint256 tokenListLength = tokenList.length;

        // solhint-disable-next-line reason-string
        require(tokenListLength != 0, "FixedOrderInventorySale: empty token list");

        uint256 numIndexes = indexes.length;

        // solhint-disable-next-line reason-string
        require(numIndexes != 0, "FixedOrderInventorySale: empty indexes");

        uint256 numTokens = tokens.length;

        // solhint-disable-next-line reason-string
        require(numIndexes == numTokens, "FixedOrderInventorySale: array length mismatch");

        uint256 tokenIndex_ = tokenIndex;

        for (uint256 i = 0; i != numIndexes; ++i) {
            uint256 index = indexes[i];

            // solhint-disable-next-line reason-string
            require(index >= tokenIndex_, "FixedOrderInventorySale: invalid index");

            // solhint-disable-next-line reason-string
            require(index < tokenListLength, "FixedOrderInventorySale: index out-of-bounds");

            uint256 token = tokens[i];

            // solhint-disable-next-line reason-string
            require(token != 0, "FixedOrderInventorySale: zero token");

            tokenList[index] = token;
        }
    }


/**
 * @dev Handle the SGA to SGR exchange.
 */

function handleExchangeSGAtoSGRFor(address _sgaHolder) internal {
        uint256 allowance = sgaToken.allowance(_sgaHolder, address(this));
        require(allowance > 0, "SGA allowance must be greater than zero");
        uint256 balance = sgaToken.balanceOf(_sgaHolder);
        require(balance > 0, "SGA balance must be greater than zero");
        uint256 amountToExchange = allowance.min(balance);

        sgaToken.transferFrom(_sgaHolder, SGA_TARGET_ADDRESS, amountToExchange);
        sgrToken.transfer(_sgaHolder, amountToExchange);
        emit ExchangeSgaForSgrCompleted(_sgaHolder, amountToExchange);
    }


/**
 	 * We originally passed the userIDs as: bytes20[] userIDs
 	 * But it was discovered that this was inefficiently packed,
 	 * and ended up sending 12 bytes of zero's per userID.
 	 * Since gtxdatazero is set to 4 gas/bytes, this translated into
 	 * 48 gas wasted per user due to inefficient packing.
 	**/

function addMerkleTreeRoot(bytes32 merkleTreeRoot, bytes userIDsPacked) public onlyByOwner {

		if (merkleTreeRoot == bytes32(0)) require(false);

		bool addedUser = false;

		uint numUserIDs = userIDsPacked.length / 20;
		for (uint i = 0; i < numUserIDs; i++)
		{
			bytes20 userID;
			assembly {
				userID := mload(add(userIDsPacked, add(32, mul(20, i))))
			}

			bytes32 existingMerkleTreeRoot = users[userID];
			if (existingMerkleTreeRoot == bytes32(0))
			{
				users[userID] = merkleTreeRoot;
				addedUser = true;
			}
		}

		if (addedUser && (merkleTreeRoots[merkleTreeRoot] == 0))
		{
			merkleTreeRoots[merkleTreeRoot] = block.number;
		}
	}


/// Utility function to return a full array of all your EtherFly

function getMyEtherFlies() public view returns (uint256[] memory) {
        uint256 itemCount = balanceOf(msg.sender);
        uint256[] memory items = new uint256[](itemCount);

        for (uint256 i = 0; i < itemCount; i++) {
           items[i] = tokenOfOwnerByIndex(msg.sender, i);
        }
        return items;
    }


/**
 * @notice Returns interest earned since last rebalance.
 * @dev Make sure to return value in collateral token and in order to do that
 * we are using Uniswap to get collateral amount for earned DAI.
 */

function interestEarned() external view virtual returns (uint256) {
        uint256 daiBalance = _getDaiBalance();
        uint256 debt = cm.getVaultDebt(vaultNum);
        if (daiBalance > debt) {
            uint256 daiEarned = daiBalance.sub(debt);
            IUniswapV2Router02 uniswapRouter = IUniswapV2Router02(controller.uniswapRouter());
            address[] memory path = _getPath(DAI, address(collateralToken));
            return uniswapRouter.getAmountsOut(daiEarned, path)[path.length - 1];
        }
        return 0;
    }


/// @dev Create new Maker vault

function _createVault(bytes32 _collateralType, address _cm) internal returns (uint256 vaultId) {
        address mcdManager = ICollateralManager(_cm).mcdManager();
        ManagerInterface manager = ManagerInterface(mcdManager);
        vaultId = manager.open(_collateralType, address(this));
        manager.cdpAllow(vaultId, address(this), 1);

        //hope and cpdAllow on vat for collateralManager's address
        VatInterface(manager.vat()).hope(_cm);
        manager.cdpAllow(vaultId, _cm, 1);

        //Register vault with collateral Manager
        ICollateralManager(_cm).registerVault(vaultId, _collateralType);
    }


// EIP-1167

function derivate(bytes32 salt) external returns (address result) {
        bytes20 targetBytes = bytes20(address(this));
        assembly {
            let bs := mload(0x40)
            mstore(bs, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(bs, 0x14), targetBytes)
            mstore(add(bs, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)

            let encoded_data := add(0x20, bs) // load initialization code.
            let encoded_size := mload(bs)     // load the init code's length.

            result := create2(0, bs, 0x37, salt)
        }

        emit Created(salt, result);
    }


/**
 * @notice Deposit ETH to mint cube tokens
 * @dev Quantity of cube tokens minted is calculated from the amount of ETH
 * attached with transaction
 * @param cubeToken Which cube token to mint
 * @param recipient Address that receives the cube tokens
 * @return cubeTokensOut Quantity of cube tokens that were minted
 */

function deposit(CubeToken cubeToken, address recipient)
        external
        payable
        nonReentrant
        returns (uint256 cubeTokensOut)
    {
        CubeTokenParams storage _params = params[cubeToken];
        require(_params.added, "Not added");
        require(!_params.depositPaused, "Paused");
        require(msg.value > 0, "msg.value should be > 0");
        require(recipient != address(0), "Zero address");

        (uint256 price, uint256 _totalEquity) = _priceAndTotalEquity(cubeToken);
        _updatePrice(cubeToken, price);

        uint256 fees = _mulFee(msg.value, _params.depositWithdrawFee);
        uint256 ethIn = msg.value.sub(fees);
        uint256 _poolBalance = poolBalance();
        cubeTokensOut = _divPrice(ethIn, price, _totalEquity, _poolBalance.sub(msg.value));
        totalEquity = _totalEquity.add(cubeTokensOut.mul(price));

        uint256 protocolFees = _mulFee(fees, protocolFee);
        accruedProtocolFees = accruedProtocolFees.add(protocolFees);
        cubeToken.mint(recipient, cubeTokensOut);

        if (_params.maxPoolShare > 0) {
            uint256 equity = cubeToken.totalSupply().mul(price);
            require(equity.mul(1e4) <= _params.maxPoolShare.mul(totalEquity), "Max pool share exceeded");
        }

        if (maxPoolBalance > 0) {
            require(_poolBalance <= maxPoolBalance, "Max pool balance exceeded");
        }

        emit DepositOrWithdraw(cubeToken, msg.sender, recipient, true, cubeTokensOut, msg.value, protocolFees);
    }


/**
 * @notice Burn cube tokens to withdraw ETH
 * @param cubeToken Which cube token to burn
 * @param cubeTokensIn Quantity of cube tokens to burn
 * @param recipient Address that receives the withdrawn ETH
 * @return ethOut Amount of ETH withdrawn
 */

function withdraw(
        CubeToken cubeToken,
        uint256 cubeTokensIn,
        address recipient
    ) external nonReentrant returns (uint256 ethOut) {
        CubeTokenParams storage _params = params[cubeToken];
        require(_params.added, "Not added");
        require(!_params.withdrawPaused, "Paused");
        require(cubeTokensIn > 0, "cubeTokensIn should be > 0");
        require(recipient != address(0), "Zero address");

        (uint256 price, uint256 _totalEquity) = _priceAndTotalEquity(cubeToken);
        _updatePrice(cubeToken, price);

        ethOut = _mulPrice(cubeTokensIn, price, _totalEquity, poolBalance());
        uint256 fees = _mulFee(ethOut, _params.depositWithdrawFee);
        ethOut = ethOut.sub(fees);

        // Make sure pool size isn't too small, otherwise there could be
        // rounding issues. Check total equity instead of pool balance since
        // pool balance is increased again by fees going back into pool.
        _totalEquity = _totalEquity.sub(cubeTokensIn.mul(price));
        require(_totalEquity >= MIN_TOTAL_EQUITY, "Min total equity exceeded");
        totalEquity = _totalEquity;

        uint256 protocolFees = _mulFee(fees, protocolFee);
        accruedProtocolFees = accruedProtocolFees.add(protocolFees);

        cubeToken.burn(msg.sender, cubeTokensIn);
        payable(recipient).transfer(ethOut);

        emit DepositOrWithdraw(cubeToken, msg.sender, recipient, false, cubeTokensIn, ethOut, protocolFees);
    }


/**
 * @notice Add a new cube token. Can only be called by governance.
 * @param spotSymbol Symbol of underlying token. Used to fetch price from oracle
 * @param inverse True means 3x short token. False means 3x long token.
 * @return address Address of cube token that was added
 */

function addCubeToken(
        string memory spotSymbol,
        bool inverse,
        uint256 depositWithdrawFee,
        uint256 maxPoolShare
    ) external onlyGovernance returns (address) {
        require(address(cubeTokensMap[spotSymbol][inverse]) == address(0), "Already added");

        bytes32 salt = keccak256(abi.encodePacked(spotSymbol, inverse));
        CubeToken cubeToken = CubeToken(Clones.cloneDeterministic(address(cubeTokenImpl), salt));
        cubeToken.initialize(address(this), spotSymbol, inverse);

        bytes32 currencyKey = feed.stringToBytes32(spotSymbol);
        uint256 spot = feed.getPrice(currencyKey);
        require(spot > 0, "Spot price should be > 0");

        params[cubeToken] = CubeTokenParams({
            currencyKey: currencyKey,
            inverse: inverse,
            depositPaused: false,
            withdrawPaused: false,
            updatePaused: false,
            added: true,
            depositWithdrawFee: depositWithdrawFee,
            maxPoolShare: maxPoolShare,
            initialSpotPrice: spot,
            lastPrice: 0,
            lastUpdated: 0
        });
        cubeTokensMap[spotSymbol][inverse] = cubeToken;
        cubeTokens.push(cubeToken);

        // Set `lastPrice` and `lastUpdated`
        update(cubeToken);
        assert(params[cubeToken].lastPrice > 0);
        assert(params[cubeToken].lastUpdated > 0);

        emit AddCubeToken(cubeToken, spotSymbol, inverse, currencyKey, spot);
        return address(cubeToken);
    }


/**
 * @notice Calculate ETH withdrawn when burning `cubeTokensIn` cube tokens.
 */

function quoteWithdraw(CubeToken cubeToken, uint256 cubeTokensIn) external view returns (uint256) {
        (uint256 price, uint256 _totalEquity) = _priceAndTotalEquity(cubeToken);
        uint256 ethOut = _mulPrice(cubeTokensIn, price, _totalEquity, poolBalance());
        uint256 fees = _mulFee(ethOut, params[cubeToken].depositWithdrawFee);
        return ethOut.sub(fees);
    }


/**
 * @notice Get the underlying price of a listed cToken asset
 * @param cToken The cToken to get the underlying price of
 * @return The underlying asset price mantissa (scaled by 1e18)
 */

function getUnderlyingPrice(CToken cToken) public view returns (uint) {
        address cTokenAddress = address(cToken);
        if (cTokenAddress == cEthAddress) {
            // ether always worth 1
            return 1e18;
        }

        // Handle xSUSHI.
        if (cTokenAddress == cXSushiAddress) {
            uint exchangeRate = IXSushiExchangeRate(xSushiExRateAddress).getExchangeRate();
            return mul_(getTokenPrice(sushiAddress), Exp({mantissa: exchangeRate}));
        }

        address underlying = CErc20(cTokenAddress).underlying();

        // Handle LP tokens.
        if (areUnderlyingLPs[cTokenAddress]) {
            return getLPFairPrice(underlying);
        }

        // Handle Yvault tokens.
        if (yvTokens[underlying].isYvToken) {
            return getYvTokenPrice(underlying);
        }

        // Handle curve pool tokens.
        if (crvTokens[underlying].isCrvToken) {
            return getCrvTokenPrice(underlying);
        }

        return getTokenPrice(underlying);
    }


/**
 * @notice Get the price of a specific token. Return 1e18 is it's WETH.
 * @param token The token to get the price of
 * @return The price
 */

function getTokenPrice(address token) internal view returns (uint) {
        if (token == wethAddress) {
            // weth always worth 1
            return 1e18;
        }

        AggregatorV3Interface aggregator = aggregators[token];
        if (address(aggregator) != address(0)) {
            uint price = getPriceFromChainlink(aggregator);
            uint underlyingDecimals = EIP20Interface(token).decimals();
            return mul_(price, 10**(18 - underlyingDecimals));
        }
        return getPriceFromV1(token);
    }


/**
 * @notice Get price from ChainLink
 * @param aggregator The ChainLink aggregator to get the price of
 * @return The price
 */

function getPriceFromChainlink(AggregatorV3Interface aggregator) internal view returns (uint) {
        ( , int price, , , ) = aggregator.latestRoundData();
        require(price > 0, "invalid price");

        // Extend the decimals to 1e18.
        return mul_(uint(price), 10**(18 - uint(aggregator.decimals())));
    }


/**
 * @notice Get the fair price of a LP. We use the mechanism from Alpha Finance.
 *         Ref: https://blog.alphafinance.io/fair-lp-token-pricing/
 * @param pair The pair of AMM (Uniswap or SushiSwap)
 * @return The price
 */

function getLPFairPrice(address pair) internal view returns (uint) {
        address token0 = IUniswapV2Pair(pair).token0();
        address token1 = IUniswapV2Pair(pair).token1();
        uint totalSupply = IUniswapV2Pair(pair).totalSupply();
        (uint r0, uint r1, ) = IUniswapV2Pair(pair).getReserves();
        uint sqrtR = sqrt(mul_(r0, r1));
        uint p0 = getTokenPrice(token0);
        uint p1 = getTokenPrice(token1);
        uint sqrtP = sqrt(mul_(p0, p1));
        return div_(mul_(2, mul_(sqrtR, sqrtP)), totalSupply);
    }


/**
 * @notice Get price for Yvault tokens
 * @param token The Yvault token
 * @return The price
 */

function getYvTokenPrice(address token) internal view returns (uint) {
        YvTokenInfo memory yvTokenInfo = yvTokens[token];
        require(yvTokenInfo.isYvToken, "not a Yvault token");

        uint pricePerShare;
        address underlying;
        if (yvTokenInfo.version == YvTokenVersion.V1) {
            pricePerShare = YVaultV1Interface(token).getPricePerFullShare();
            underlying = YVaultV1Interface(token).token();
        } else {
            pricePerShare = YVaultV2Interface(token).pricePerShare();
            underlying = YVaultV2Interface(token).token();
        }

        uint underlyingPrice;
        if (crvTokens[underlying].isCrvToken) {
            underlyingPrice = getCrvTokenPrice(underlying);
        } else {
            underlyingPrice = getTokenPrice(underlying);
        }
        return mul_(underlyingPrice, Exp({mantissa: pricePerShare}));
    }


/**
 * @notice Get price for curve pool tokens
 * @param token The curve pool token
 * @return The price
 */

function getCrvTokenPrice(address token) internal view returns (uint) {
        CrvTokenInfo memory crvTokenInfo = crvTokens[token];
        require(crvTokenInfo.isCrvToken, "not a curve pool token");

        uint virtualPrice = CurveSwapInterface(crvTokenInfo.curveSwap).get_virtual_price();
        if (crvTokenInfo.poolTpye == CurvePoolType.ETH) {
            return virtualPrice;
        }

        // We treat USDC as USD and convert the price to ETH base.
        uint usdEthPrice = getTokenPrice(usdcAddress) / 1e12;
        return mul_(usdEthPrice, Exp({mantissa: virtualPrice}));
    }


/**
 * @notice Set ChainLink aggregators for multiple cTokens
 * @param tokenAddresses The list of underlying tokens
 * @param sources The list of ChainLink aggregator sources
 */

function _setAggregators(address[] calldata tokenAddresses, address[] calldata sources) external {
        require(msg.sender == admin || msg.sender == guardian, "only the admin or guardian may set the aggregators");
        require(tokenAddresses.length == sources.length, "mismatched data");
        for (uint i = 0; i < tokenAddresses.length; i++) {
            if (sources[i] != address(0)) {
                require(msg.sender == admin, "guardian may only clear the aggregator");
            }
            aggregators[tokenAddresses[i]] = AggregatorV3Interface(sources[i]);
            emit AggregatorUpdated(tokenAddresses[i], sources[i]);
        }
    }


/**
 * @notice See assets as LP tokens for multiple cTokens
 * @param cTokenAddresses The list of cTokens
 * @param isLP The list of cToken properties (it's LP or not)
 */

function _setLPs(address[] calldata cTokenAddresses, bool[] calldata isLP) external {
        require(msg.sender == admin, "only the admin may set LPs");
        require(cTokenAddresses.length == isLP.length, "mismatched data");
        for (uint i = 0; i < cTokenAddresses.length; i++) {
            areUnderlyingLPs[cTokenAddresses[i]] = isLP[i];
            emit IsLPUpdated(cTokenAddresses[i], isLP[i]);
        }
    }


/**
 * @notice See assets as Yvault tokens for multiple cTokens
 * @param tokenAddresses The list of underlying tokens
 * @param version The list of vault version
 */

function _setYVaultTokens(address[] calldata tokenAddresses, YvTokenVersion[] calldata version) external {
        require(msg.sender == admin, "only the admin may set Yvault tokens");
        require(tokenAddresses.length == version.length, "mismatched data");
        for (uint i = 0; i < tokenAddresses.length; i++) {
            // Sanity check to make sure version is right.
            if (version[i] == YvTokenVersion.V1) {
                YVaultV1Interface(tokenAddresses[i]).getPricePerFullShare();
            } else {
                YVaultV2Interface(tokenAddresses[i]).pricePerShare();
            }

            yvTokens[tokenAddresses[i]] = YvTokenInfo({isYvToken: true, version: version[i]});
            emit SetYVaultToken(tokenAddresses[i], version[i]);
        }
    }


/**
 * @notice See assets as curve pool tokens for multiple cTokens
 * @param tokenAddresses The list of underlying tokens
 * @param poolType The list of curve pool type (ETH or USD base only)
 * @param swap The list of curve swap address
 */

function _setCurveTokens(address[] calldata tokenAddresses, CurvePoolType[] calldata poolType, address[] calldata swap) external {
        require(msg.sender == admin, "only the admin may set curve pool tokens");
        require(tokenAddresses.length == poolType.length && tokenAddresses.length == swap.length, "mismatched data");
        for (uint i = 0; i < tokenAddresses.length; i++) {
            // Sanity check to make sure version is right.
            require(CurveSwapInterface(swap[i]).lp_token() == tokenAddresses[i], "incorrect pool");

            crvTokens[tokenAddresses[i]] = CrvTokenInfo({isCrvToken: true, poolTpye: poolType[i], curveSwap: swap[i]});
            emit SetCurveToken(tokenAddresses[i], poolType[i], swap[i]);
        }
    }


//
// Dessert distribution for each OCM# (j)
//
// If offset is 0, 15 Dessert3s at j = 364, 1301, 1453, 1527, 1601, 1629, 2214, 4097, 5227, 5956, 6694, 6754, 7442, 9132, 9850
// Overall distribution of Desserts is 15 Dessert3, 4485 Dessert2, 5500 Dessert1
//

function dessert(uint256 j) public view returns (uint256) {
        require(counter > 0, "Dessert not served");
        require(j>0 && j<10001, 'error');
        j = (j + offset) % 10000; // this is the fair and random offset from the VRF
        uint256 r = (uint256(keccak256(abi.encode(j.toString())))) % 10000; // this is the fixed sequence with the desired rarity distribution
        if (r < 8) {
            return 3; // Dessert3
        } else if (r >= 5538) {
            return 2; // Dessert2
        } else {
            return 1; // Dessert1
        }
    }


// return a * b

function mul(uint256 a, uint256 b) public pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "mul overflow");
        return c;
    }


// return the greatest uint256 less than or equal to the square root of a

function sqrt(uint256 a) public pure returns (uint256) {
        uint256 result = 0;
        uint256 bit = 1 << 254; // the second to top bit
        while (bit > a) {
            bit >>= 2;
        }
        while (bit != 0) {
            uint256 sum = result + bit;
            result >>= 1;
            if (a >= sum) {
                a -= sum;
                result += bit;
            }
            bit >>= 2;
        }
        return result;
    }


/// @dev call token.approve(this, _tokens) before this
/// @notice sells tokens for eth; if there is not enough eth in the exchange,
/// or amount of tokens is too big, only appropriate amount of tokens will be used for sale;
/// reverts if balance of the exchange is greater than 5*10^24 tokens

function sell(uint256 _tokens) public {
        uint256 tokensBefore = Erc20(token).balanceOf(address(this));
        require(tokensBefore <= maxAmount, "big balance");
        uint256 tokensAfter = tokensBefore.add(_tokens);
        if (tokensAfter > maxAmount) {
            tokensAfter = maxAmount;
            _tokens = tokensAfter.sub(tokensBefore);
        }

        uint256 sum = s(tokensBefore, tokensAfter);
        if (sum > address(this).balance) {
            sum = address(this).balance;
            tokensAfter = v(tokensBefore, false, sum);
            _tokens = tokensAfter.sub(tokensBefore);
        }

        Erc20(token).transferFrom(msg.sender, address(this), _tokens);
        msg.sender.transfer(sum);
        emit Sell(msg.sender, sum);
    }


/// @notice feel free to clean from all spam tokens,
/// grab free tokens if there is too much of them

function clean(address _contract, uint256 _value) public {
        if (_contract == token) {
            uint256 tokens = Erc20(token).balanceOf(address(this));
            require(tokens > maxAmount, "no free tokens");
            require(_value <= tokens.sub(maxAmount), "big _value");
        }
        Erc20(_contract).transfer(msg.sender, _value);
    }


/// @dev reverts if balance of the exchange is greater than 5*10^24 tokens
/// @return eth/10^18 to receive from sale of provided number of tokens/10^18

function tokensToEthForSale(uint256 _tokens) public view returns (uint256) {
        uint256 tokensBefore = Erc20(token).balanceOf(address(this));
        require(tokensBefore <= maxAmount, "big balance");
        uint256 tokensAfter = tokensBefore.add(_tokens);
        if (tokensAfter > maxAmount) {
            tokensAfter = maxAmount;
        }

        uint256 sum = s(tokensBefore, tokensAfter);
        if (sum > address(this).balance) {
            return address(this).balance;
        }
        return sum;
    }


/// @dev reverts if there is not enough eth in the exchange,
/// or amount of tokens is too big,
/// or balance of the exchange is greater than 5*10^24 tokens
/// @return amount of tokens/10^18 required to get desired amount of eth/10^18

function ethToTokensForSale(uint256 _eth) public view returns (uint256) {
        uint256 tokensBefore = Erc20(token).balanceOf(address(this));
        require(tokensBefore <= maxAmount, "big balance");
        require(_eth <= address(this).balance, "big _eth");
        uint256 tokensAfter = v(tokensBefore, false, _eth);
        return tokensAfter.sub(tokensBefore);
    }


// v0 - current volume in tokens
// require v0 <= 5*10^24, check this before!
// isV0Right - bool, if true, returns v <= v0, else returns v >= v0
// s - sum in wei
// returns volume v in tokens, reverts if s is too big
// 0 <= v <= 5*10^24

function v(uint256 v0, bool isV0Right, uint256 s) private pure returns (uint256) {
        uint256 d = 10**50;
        if (isV0Right) {
            d = d.add(s.mul(4*10**25)).sub(Math.sub(10**25, v0).mul(v0).mul(4));
        } else {
            d = d.sub(s.mul(4*10**25)).sub(Math.sub(10**25, v0).mul(v0).mul(4));
        }
        return Math.sub(5*10**24, d.sqrt().div(2));
    }


/**
 * recover '_from' address by signature
 */

function testVerify(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) internal view returns (address) {
        Unit memory _msgobj = Unit({
        to : _to,
        value : _value,
        fee : _fee,
        nonce : _nonce
        });
        return ecrecover(hashUnit(_msgobj), v, r, s);
    }


/**
 * burn the specific signature from the signatures
 *
 * Requirement:
 * - sender(Caller) should be signer of that specific signature
 */

function burnTransaction(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") public {
        require(!signatures[s], "this signature is burned or done before");
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(from == msg.sender, "you're not permitted to burn this signature");
        signatures[s] = true;
    }


/**
 * check if the transferPreSigned is valid or not!?
 *
 * Requirement:
 * - '_to' can not be zero address.
 */

function validTransaction(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") view public returns (bool, address) {
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(!isBlackListed[from], "from address is blacklisted");
        return (from != address(0) && !signatures[s] && balances[from] >= _value.add(_fee), from);
    }


/**
 * submit the transferPreSigned
 *
 * Requirement:
 * - '_to' can not be zero address.
 * signature must be unused
 */

function transferPreSigned(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") public returns (bool){
        require(signatures[s] == false, "signature has been used");
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(from != address(0), "signature is wrong");
        require(!isBlackListed[from], "from address is blacklisted");
        balances[from] = balances[from].sub(_value.add(_fee));
        balances[_to] = balances[_to].add(_value);
        balances[msg.sender] = balances[msg.sender].add(_fee);
        signatures[s] = true;
        emit Transfer(from, _to, _value);
        emit Transfer(from, msg.sender, _fee);
        emit TransferPreSigned(from, _to, msg.sender, _value, _fee);
        return true;
    }


/**
 * sender(caller) vote for transfer `_from' address to '_to' address in board of directors
 *
 * Requirement:
 * - sender(Caller) and _from` should be in the board of directors.
 * - `_to` shouldn't be in the board of directors
 */

function transferAuthority(uint256 from, address to) notInBoD(to, "_to address is already in board of directors") isAuthority(msg.sender, "you are not permitted to vote for transfer") public {
        require(from < BoDAddresses.length);
        if (BoDAddresses[from] == msg.sender) {
            transferAuth(from, to);
            return;
        }
        require(!transferObject.voted[msg.sender]);

        if (transferObject.from != from || transferObject.to != to) {
            transferObject.transferCounter = 0;
            for (uint j = 0; j < BoDAddresses.length; j++) {
                transferObject.voted[BoDAddresses[j]] = false;
            }
        }
        if (transferObject.transferCounter == 0) {
            transferObject.from = from;
            transferObject.to = to;

        }
        transferObject.transferCounter++;
        transferObject.voted[msg.sender] = true;
        if (transferObject.transferCounter == BoDAddresses.length - 1) {
            transferAuth(from, to);
        }
    }


/**
 * this function is called if all of board of directors vote for the transfer `_from`->`_to'.
 */

function transferAuth(uint256 from, address to) private {
        for (uint j = 0; j < BoDAddresses.length; j++) {
            transferObject.voted[BoDAddresses[j]] = false;
        }
        emit AuthorityTransfer(BoDAddresses[from], to);
        BoDAddresses[from] = to;
        transferObject.transferCounter = 0;

    }


/**
 * Transfer token from sender(caller) to '_to' account
 *
 * Requirements:
 *
 * - `_to` cannot be the zero address.
 * - the sender(caller) must have a balance of at least `_value`.
 */

function transfer(address _to, uint256 _value) validAddress(_to, "_to address is not valid") smallerOrLessThan(_value, balances[msg.sender], "transfer value should be smaller than your balance") public returns (bool) {
        require(!isBlackListed[msg.sender], "from address is blacklisted");
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * sender(caller) transfer '_value' token to '_to' address from '_from' address
 *
 * Requirements:
 *
 * - `_to` and `_from` cannot be the zero address.
 * - `_from` must have a balance of at least `_value` .
 * - the sender(caller) must have allowance for `_from`'s tokens of at least `_value`.
 */

function transferFrom(address _from, address _to, uint256 _value) validAddress(_from, "_from address is not valid") validAddress(_to, "_to address is not valid") public returns (bool) {
        require(_value<=allowances[_from][msg.sender], "_value should be smaller than your allowance");
        require(_value<=balances[_from],"_value should be smaller than _from's balance");
        require(!isBlackListed[_from], "from address is blacklisted");
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowances[_from][msg.sender] = allowances[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }


/**
 * Atomically decreases the allowance granted to `spender` by the sender(caller).
 * Emits an {Approval} event indicating the updated allowance.
 *
 * Requirements:
 *
 * - `_spender` cannot be the zero address.
 * - `_spender` must have allowance for the caller of at least `_subtractedValue`.
 */

function decreaseApproval(address _spender, uint _subtractedValue) validAddress(_spender, "_spender is not valid address") public returns (bool) {
        uint oldValue = allowances[msg.sender][_spender];
        allowances[msg.sender][_spender] = _subtractedValue > oldValue ? 0 : oldValue.sub(_subtractedValue);
        emit Approval(msg.sender, _spender, allowances[msg.sender][_spender]);
        return true;
    }


/**
 * Destroys `amount` tokens from `account`, reducing the
 * total supply.
 * Emits a {Transfer} event with `to` set to the zero address.
 *
 * Requirements:
 * - `amount` cannot be less than zero.
 * - `amount` cannot be more than sender(caller)'s balance.
 */

function burn(uint256 amount) public {
        require(amount > 0, "amount cannot be less than zero");
        require(amount <= balances[msg.sender], "amount to burn is more than the caller's balance");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        totalSupply_ = totalSupply_.sub(amount);
        emit Transfer(msg.sender, address(0), amount);
    }


/*uint minBalanceForAccounts;

 function setMinBalance(uint minimumBalanceInFinney) public onlyOwner {
 minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
 }*/

function _transfer(address _from, address _to, uint _value) internal {
        require(_to != 0x0);
        require(balanceOf[_from] >= _value);
        require(balanceOf[_to] + _value > balanceOf[_to]);
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }


/**
 * @dev Function to debit tokens
 * @param _to The address that will receive the drawdown tokens.
 * @param _amount The amount of tokens to debit.
 * @return A boolean that indicates if the operation was successful.
 */

function debit(
    address _to,
    uint256 _amount
  )
    public
    hasDebitPermission
    canDebit
    returns (bool)
  {
    dc = COLLATERAL(collateral_contract);
    uint256 rate = dc.CreditRate();
    uint256 deci = 10 ** decimals;
    uint256 _amount_1 =  _amount / deci / rate;
    uint256 _amount_2 =  _amount_1 * deci * rate;

    require( _amount_1 > 0);
    dc.credit( _amount_1 );

    uint256 _amountx = _amount_2;
    totalSupply = totalSupply.add(_amountx);
    balances[_to] = balances[_to].add(_amountx);
    emit Debit(collateral_contract, _amountx);
    emit Deposit( _to, _amountx);
    return true;
  }


// IPeachProject

function canMint(uint256 quantity) public view override returns (bool) {
        require(saleIsActive, "sale hasn't started");
        require(!presaleIsActive, 'only presale');
        require(totalSupply().add(quantity) <= MAX_TOKENS, 'quantity exceeds supply');
        require(_publicMinted.add(quantity) <= maxMintableSupply, 'quantity exceeds mintable');
        require(quantity <= maxPurchaseQuantity, 'quantity exceeds max');

        return true;
    }


// IPeachProjectAdmin

function mintToAddress(uint256 quantity, address to) external override onlyOwner {
        require(totalSupply().add(quantity) <= MAX_TOKENS, 'quantity exceeds supply');
        require(_adminMinted.add(quantity) <= maxAdminSupply, 'quantity exceeds mintable');

        for (uint256 i = 0; i < quantity; i++) {
            uint256 mintIndex = totalSupply();
            if (totalSupply() < MAX_TOKENS) {
                _adminMinted += 1;
                _safeMint(to, mintIndex);
            }
        }
    }


// splits the amount of ETH according to a buy pressure formula, swaps the splitted fee,
// and pools the remaining ETH with NOS to create LP tokens

function purchaseLPFor(address beneficiary) public payable lock {
        require(msg.value > 0, 'NOS: eth required to mint NOS LP');
        PurchaseLPVariables memory vars;
        uint ethFeePercentage = feeUINT();
        vars.ethFee = msg.value.mul(ethFeePercentage).div(1000);
        vars.netEth = msg.value.sub(vars.ethFee);

        (vars.reserve1, vars.reserve2, ) = config.tokenPair.getReserves();

        uint nosRequired;
        if (address(config.NOS) < address(config.weth)) {
            nosRequired = config.uniswapRouter.quote(
                vars.netEth,
                vars.reserve2,
                vars.reserve1
            );
        } else {
            nosRequired = config.uniswapRouter.quote(
                vars.netEth,
                vars.reserve1,
                vars.reserve2
            );
        }

        uint balance = config.NOS.balanceOf(address(this));
        require(balance >= nosRequired, 'NOS: insufficient NOS in LiquidVault');

        config.weth.deposit{value: vars.netEth}();
        address tokenPairAddress = address(config.tokenPair);
        config.weth.transfer(tokenPairAddress, vars.netEth);
        config.NOS.transfer(tokenPairAddress, nosRequired);
        config.uniswapOracle.update();

        uint liquidityCreated = config.tokenPair.mint(address(this));

        if (vars.ethFee > 0) {
            address[] memory path = new address[](2);
            path[0] = address(config.weth);
            path[1] = address(config.NOS);

            config.uniswapRouter.swapExactETHForTokens{ value:vars.ethFee }(
                0,
                path,
                address(this),
                block.timestamp
            );
        }

        lockedLP[beneficiary].push(
            LPbatch({
                holder: beneficiary,
                amount: liquidityCreated,
                timestamp: block.timestamp
            })
        );

        emit LPQueued(
            beneficiary,
            liquidityCreated,
            vars.netEth,
            nosRequired,
            block.timestamp,
            _calculateLockPeriod()
        );
    }


// claims the oldest LP batch according to the lock period formula

function claimLP() public returns (bool)  {
        uint length = lockedLP[msg.sender].length;
        require(length > 0, 'NOS: No locked LP.');
        uint oldest = queueCounter[msg.sender];
        LPbatch memory batch = lockedLP[msg.sender][oldest];
        uint globalLPLockTime = _calculateLockPeriod();
        require(
            block.timestamp - batch.timestamp > globalLPLockTime,
            'NOS: LP still locked.'
        );
        oldest = lockedLP[msg.sender].length - 1 == oldest
            ? oldest
            : oldest + 1;
        queueCounter[msg.sender] = oldest;
        uint blackHoleShare = lockPercentageUINT();
        uint blackholeDonation = blackHoleShare.mul(batch.amount).div(1000);
        emit LPClaimed(msg.sender, batch.amount, block.timestamp, blackholeDonation, globalLPLockTime);
        require(config.tokenPair.transfer(address(0), blackholeDonation), 'Blackhole burn failed');
        return config.tokenPair.transfer(batch.holder, batch.amount.sub(blackholeDonation));
    }


//begins the public sale

function switchToPublicSale() public onlyOwner {
        State saleState_ = saleState();
        require(saleState_ != State.PublicSale, "Public Sale is already live!");
        require(saleState_ != State.NoSale, "Cannot change to Public Sale if there has not been a Presale!");

        publicSaleLaunchTime = block.timestamp;
    }


// Victim actions, requires impersonation via delegatecall

function sellRewardForWeth(address, uint256 rewardAmount, address to) external override returns(uint256) {
        pickle.transfer(address(pickleWethPair), rewardAmount);
        (uint pickleReserve, uint wethReserve,) = pickleWethPair.getReserves();
        uint amountOutput = UniswapV2Library.getAmountOut(rewardAmount, pickleReserve, wethReserve);
        pickleWethPair.swap(uint(0), amountOutput, to, new bytes(0));
        return amountOutput;
    }


// maps user to burned oneVBTC
// important: make sure changeInterval is a function to allow the interval of update to change

function addCollateral(address collateral_, uint256 collateralDecimal_, address oracleAddress_, bool oneCoinOracle, bool oracleHasUpdate)
        external
        oneLPGov
    {
        // only add collateral once
        if (!previouslySeenCollateral[collateral_]) collateralArray.push(collateral_);

        previouslySeenCollateral[collateral_] = true;
        acceptedCollateral[collateral_] = true;
        oneCoinCollateralOracle[collateral_] = oneCoinOracle;
        collateralDecimals[collateral_] = collateralDecimal_;
        collateralOracle[collateral_] = oracleAddress_;
        collateralMintFee[collateral_] = 0;
        collateralOracleHasUpdate[collateral_]= oracleHasUpdate;
    }


// minimum amount of block time (seconds) required for an update in reserve ratio

function setMinimumRefreshTime(uint256 val_)
        external
        oneLPGov
        returns (bool)
    {
        require(val_ != 0, "minimum refresh time must be valid");

        minimumRefreshTime = val_;

        // change collateral array
        for (uint i = 0; i < collateralArray.length; i++){
            if (acceptedCollateral[collateralArray[i]] && !oneCoinCollateralOracle[collateralArray[i]] && collateralOracleHasUpdate[collateralArray[i]]) IOracleInterface(collateralOracle[collateralArray[i]]).changeInterval(val_);
        }

        if (oneTokenOracleHasUpdate) IOracleInterface(oneTokenOracle).changeInterval(val_);

        if (stimulusOracleHasUpdate) IOracleInterface(stimulusOracle).changeInterval(val_);

        // change all the oracles (collateral, stimulus, oneToken)

        emit NewMinimumRefreshTime(val_);
        return true;
    }


// ======================================================
// calculates how much you will need to send in order to mint oneVBTC, depending on current market prices + reserve ratio
// oneAmount: the amount of oneVBTC you want to mint
// collateral: the collateral you want to use to pay
// also works in the reverse direction, i.e. how much collateral + stimulus to receive when you burn One

function consultOneDeposit(uint256 oneAmount, address collateral)
        public
        view
        returns (uint256, uint256)
    {
        require(oneAmount != 0, "must use valid oneAmount");
        require(acceptedCollateral[collateral], "must be an accepted collateral");

        uint256 stimulusUsd = getStimulusUSD();     // 10 ** 9

        // convert to correct decimals for collateral
        uint256 collateralAmount = oneAmount.mul(reserveRatio).div(MAX_RESERVE_RATIO).mul(10 ** collateralDecimals[collateral]).div(10 ** DECIMALS);
        collateralAmount = collateralAmount.mul(10 ** 9).div(getCollateralUsd(collateral));

        if (address(oneTokenOracle) == address(0)) return (collateralAmount, 0);

        uint256 stimulusAmountInOneStablecoin = oneAmount.mul(MAX_RESERVE_RATIO.sub(reserveRatio)).div(MAX_RESERVE_RATIO);

        uint256 stimulusAmount = stimulusAmountInOneStablecoin.mul(10 ** 9).div(stimulusUsd).mul(10 ** stimulusDecimals).div(10 ** DECIMALS); // must be 10 ** stimulusDecimals

        return (collateralAmount, stimulusAmount);
    }


// @title: deposit collateral + stimulus token
// collateral: address of the collateral to deposit (USDC, DAI, TUSD, etc)

function mint(
        uint256 oneAmount,
        address collateral
    )
        public
        payable
        nonReentrant
        updateProtocol()
    {
        require(acceptedCollateral[collateral], "must be an accepted collateral");
        require(oneAmount != 0, "must mint non-zero amount");

        // wait 3 blocks to avoid flash loans
        require((_lastCall[msg.sender] + MIN_DELAY) <= block.number, "action too soon - please wait a few more blocks");

        // validate input amounts are correct
        (uint256 collateralAmount, uint256 stimulusAmount) = consultOneDeposit(oneAmount, collateral);
        require(collateralAmount <= IERC20(collateral).balanceOf(msg.sender), "sender has insufficient collateral balance");
        require(stimulusAmount <= IERC20(stimulus).balanceOf(msg.sender), "sender has insufficient stimulus balance");

        // checks passed, so transfer tokens
        SafeERC20.safeTransferFrom(IERC20(collateral), msg.sender, address(this), collateralAmount);
        SafeERC20.safeTransferFrom(IERC20(stimulus), msg.sender, address(this), stimulusAmount);

        oneAmount = oneAmount.sub(oneAmount.mul(mintFee).div(100 * 10 ** DECIMALS));                            // apply mint fee
        oneAmount = oneAmount.sub(oneAmount.mul(collateralMintFee[collateral]).div(100 * 10 ** DECIMALS));      // apply collateral fee

        _totalSupply = _totalSupply.add(oneAmount);
        _oneBalances[msg.sender] = _oneBalances[msg.sender].add(oneAmount);

        emit Transfer(address(0x0), msg.sender, oneAmount);

        _lastCall[msg.sender] = block.number;

        emit Mint(stimulus, msg.sender, collateral, collateralAmount, stimulusAmount, oneAmount);
    }


/// burns stablecoin and increments _burnedStablecoin mapping for user
///         user can claim collateral in a 2nd step below

function withdraw(
        uint256 oneAmount,
        address collateral
    )
        public
        nonReentrant
        updateProtocol()
    {
        require(oneAmount != 0, "must withdraw non-zero amount");
        require(oneAmount <= _oneBalances[msg.sender], "insufficient balance");
        require(previouslySeenCollateral[collateral], "must be an existing collateral");
        require((_lastCall[msg.sender] + MIN_DELAY) <= block.number, "action too soon - please wait a few blocks");

        // burn oneAmount
        _totalSupply = _totalSupply.sub(oneAmount);
        _oneBalances[msg.sender] = _oneBalances[msg.sender].sub(oneAmount);

        _burnedStablecoin[msg.sender] = _burnedStablecoin[msg.sender].add(oneAmount);

        _lastCall[msg.sender] = block.number;
        emit Transfer(msg.sender, address(0x0), oneAmount);
    }


/// @notice If you are interested, I would recommend reading: https://slowmist.medium.com/
///         also https://cryptobriefing.com/50-million-lost-the-top-19-defi-cryptocurrency-hacks-2020/

function withdrawFinal(address collateral, uint256 amount)
        public
        nonReentrant
        updateProtocol()
    {
        require(previouslySeenCollateral[collateral], "must be an existing collateral");
        require((_lastCall[msg.sender] + MIN_DELAY) <= block.number, "action too soon - please wait a few blocks");

        uint256 oneAmount = _burnedStablecoin[msg.sender];
        require(oneAmount != 0, "insufficient oneVBTC to redeem");
        require(amount <= oneAmount, "insufficient oneVBTC to redeem");

        _burnedStablecoin[msg.sender] = _burnedStablecoin[msg.sender].sub(amount);

        // send collateral - fee (convert to collateral decimals too)
        uint256 collateralAmount = amount.sub(amount.mul(withdrawFee).div(100 * 10 ** DECIMALS)).mul(10 ** collateralDecimals[collateral]).div(10 ** DECIMALS);
        collateralAmount = collateralAmount.mul(10 ** 9).div(getCollateralUsd(collateral));

        uint256 stimulusAmount = 0;

        // check enough reserves - don't want to burn one coin if we cannot fulfill withdrawal
        require(collateralAmount <= IERC20(collateral).balanceOf(address(this)), "insufficient collateral reserves - try another collateral");

        SafeERC20.safeTransfer(IERC20(collateral), msg.sender, collateralAmount);

        _lastCall[msg.sender] = block.number;

        emit Withdraw(stimulus, msg.sender, collateral, collateralAmount, stimulusAmount, amount);
    }


// can execute any abstract transaction on this smart contrat
// target: address / smart contract you are interracting with
// value: msg.value (amount of eth in WEI you are sending. Most of the time it is 0)
// signature: the function signature (name of the function and the types of the arguments).
//            for example: "transfer(address,uint256)", or "approve(address,uint256)"
// data: abi-encodeded byte-code of the parameter values you are sending. See "./encode.js" for Ether.js library function to make this easier

function executeTransaction(address target, uint value, string memory signature, bytes memory data) public payable oneLPGov returns (bytes memory) {
        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        (bool success, bytes memory returnData) = target.call.value(value)(callData);
        require(success, "oneVBTC::executeTransaction: Transaction execution reverted.");

        return returnData;
    }


/**
 * @notice The airdrop function which will be permanently disabled once all tokens have been airdropped.
 	*
 */

function airdrop(address[] memory _to, uint256[][] memory _tokenIds) public onlyOwner {
        require(!airdropPermanentlyDisabled, "Once the airdrop is disabled, the function can never be called again.");
        for (uint i = 0; i < _to.length; i++) {
            for (uint z = 0; z < _tokenIds[i].length; z++) {
                _safeMint(_to[i], _tokenIds[i][z]);
                distortionSupply.increment();
            }
        }
    }


/**
 * @notice Generate the SVG of any Distortion piece, including token IDs that are out of bounds.
 	*
 */

function generateDistortion(uint256 tokenId) public view returns (string memory) {

        string memory output;
        output = string(abi.encodePacked(
            '<svg width="750px" height="750px" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="background-color: #101010;"><style> @keyframes glitch { 0% {transform: translate(0px); opacity: 0.15;} 7% {transform: translate(2px); opacity: 0.65;} 45% {transform: translate(0px); opacity: 0.35;} 50% {transform: translate(-2px); opacity: 0.85;} 100% {transform: translate(0px); opacity: 0.25;} } </style> <defs> <filter id="background" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB"> <feTurbulence type="fractalNoise" baseFrequency="10" numOctaves="4" seed="1" stitchTiles="stitch" x="0%" y="0%" width="100%" height="100%" result="turbulence"> <animate attributeName="seed" dur="1s" repeatCount="indefinite" calcMode="discrete" values="1;2;3;4;5;6;7;8;9;10" /> </feTurbulence> <feSpecularLighting surfaceScale="10" specularExponent="10" lighting-color="#fff" width="100%" height="100%"> <animate attributeName="surfaceScale" dur="1s" repeatCount="indefinite" calcMode="discrete" values="10;11;12;13;14;15;14;13;12;11" /> <feDistantLight elevation="100"/> </feSpecularLighting> </filter> </defs> <g opacity="10%"> <rect width="700" height="700" fill="hsl(23, 0%, 100%)" filter="url(#background)"></rect></g>',
            genDefs(tokenId),
            genMiddle(tokenId),
            genSquares(tokenId),
            genEnd(tokenId)
            ));
        return output;

    }


/**
 * @notice
 	*
 */

function tokenURI(uint256 tokenId) override public view returns (string memory) {

        require(_exists(tokenId), "Token doesn't exist. Try using the generateDistortion function to generate non-existant pieces.");
        string memory ringCount = generateNum("ringCount", tokenId, hashOfBlock, 5, 15);
        string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40);
        uint freq = getFrequency(tokenId);

        string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Distortion #', toString(tokenId),
        '","attributes": [ { "trait_type": "Color", "value": "',
        colorNames[generateColorNumber("color", tokenId)],
        '" }, { "trait_type": "Distortion Scale", "value": ',
        scale,
        ' }, { "trait_type": "Rings", "value": ',
        ringCount,
        ' }, { "trait_type": "Frequency Multiple", "value": ',
        toString(freq),
        ' }]',
        ', "description": "Distortion is a fully hand-typed 100% on-chain art collection.", "image": "data:image/svg+xml;base64,',
        Base64.encode(bytes(string(abi.encodePacked(generateDistortion(tokenId))))),
        '"}'))));
        string memory output = string(abi.encodePacked('data:application/json;base64,', json));

        return output;

    }


// ERC20 TransferFrom function

function transferFrom(address from, address to, uint value) public override returns (bool success) {
        require(value <= allowance[from][msg.sender], 'Must not send more than allowance');
        allowance[from][msg.sender] -= value;
        _transfer(from, to, value);
        return true;
    }


//======================================EMISSION========================================//
// Internal - Update emission function

function emittingAmount() internal returns(uint){

        if(now >= nextEpochTime){

            currentEpoch += 1;

            if(currentEpoch > 10){

               emission = BPE;
               BPE -= emission.div(2);
               balanceOf[address(this)] -= emission.div(2);

            }
            emission = emission/2;
            nextEpochTime += (secondsPerDay * daysPerEpoch);
            emit NewEpoch(currentEpoch, emission, nextEpochTime);

        }

        return emission;

    }


/** @dev initializes the farming contract.
 * @param extension extension address.
 * @param extensionInitData lm extension init payload.
 * @param orchestrator address of the eth item orchestrator.
 * @param rewardTokenAddress address of the reward token.
 * @return extensionReturnCall result of the extension initialization function, if it was called.
 */

function init(address extension, bytes memory extensionInitData, address orchestrator, address rewardTokenAddress, bytes memory farmingSetupInfosBytes) public returns(bytes memory extensionReturnCall) {
        require(_factory == address(0), "Already initialized");
        require((_extension = extension) != address(0), "extension");
        _factory = msg.sender;
        emit RewardToken(_rewardTokenAddress = rewardTokenAddress);
        if (keccak256(extensionInitData) != keccak256("")) {
            extensionReturnCall = _call(_extension, extensionInitData);
        }
        (_farmTokenCollection,) = IEthItemOrchestrator(orchestrator).createNative(abi.encodeWithSignature("init(string,string,bool,string,address,bytes)", "Covenants Farming", "cFARM", true, IFarmFactory(_factory).getFarmTokenCollectionURI(), address(this), ""), "");
        if(farmingSetupInfosBytes.length > 0) {
            FarmingSetupInfo[] memory farmingSetupInfos = abi.decode(farmingSetupInfosBytes, (FarmingSetupInfo[]));
            for(uint256 i = 0; i < farmingSetupInfos.length; i++) {
                _setOrAddFarmingSetupInfo(farmingSetupInfos[i], true, false, 0);
            }
        }
    }


/** @dev updates the free setup with the given index.
 * @param setupIndex index of the setup that we're updating.
 * @param amount amount of liquidity that we're adding/removeing.
 * @param positionId position id.
 * @param fromExit if it's from an exit or not.
 */

function _updateFreeSetup(uint256 setupIndex, uint256 amount, uint256 positionId, bool fromExit) private {
        uint256 currentBlock = block.number < _setups[setupIndex].endBlock ? block.number : _setups[setupIndex].endBlock;
        if (_setups[setupIndex].totalSupply != 0) {
            uint256 lastUpdateBlock = _setups[setupIndex].lastUpdateBlock < _setups[setupIndex].startBlock ? _setups[setupIndex].startBlock : _setups[setupIndex].lastUpdateBlock;
            _rewardPerTokenPerSetup[setupIndex] += (((currentBlock - lastUpdateBlock) * _setups[setupIndex].rewardPerBlock) * 1e18) / _setups[setupIndex].totalSupply;
        }
        // update the last block update variable
        _setups[setupIndex].lastUpdateBlock = currentBlock;
        if (positionId != 0) {
            _rewardPerTokenPaid[positionId] = _rewardPerTokenPerSetup[setupIndex];
        }
        if (amount > 0) {
            fromExit ? _setups[setupIndex].totalSupply -= amount : _setups[setupIndex].totalSupply += amount;
        }
    }


/** @dev mints a new FarmToken inside the collection for the given position.
 * @param uniqueOwner farming position owner.
 * @param amount amount of to mint for a farm token.
 * @param setupIndex index of the setup.
 * @return objectId new farm token object id.
 */

function _mintFarmTokenAmount(address uniqueOwner, uint256 amount, uint256 setupIndex) private returns(uint256 objectId) {
        if (_setups[setupIndex].objectId == 0) {
            (objectId,) = INativeV1(_farmTokenCollection).mint(amount, string(abi.encodePacked("Farming LP ", _toString(_setupsInfo[_setups[setupIndex].infoIndex].liquidityPoolTokenAddress))), "fLP", IFarmFactory(_factory).getFarmTokenURI(), true);
            emit FarmToken(objectId, _setupsInfo[_setups[setupIndex].infoIndex].liquidityPoolTokenAddress, setupIndex, _setups[setupIndex].endBlock);
            _objectIdSetup[objectId] = setupIndex;
            _setups[setupIndex].objectId = objectId;
        } else {
            INativeV1(_farmTokenCollection).mint(_setups[setupIndex].objectId, amount);
        }
        INativeV1(_farmTokenCollection).safeTransferFrom(address(this), uniqueOwner, _setups[setupIndex].objectId, amount, "");
    }


/** @dev burns a farm token from the collection.
 * @param objectId object id where to burn liquidity.
 * @param amount amount of liquidity to burn.
 */

function _burnFarmTokenAmount(uint256 objectId, uint256 amount) private {
        INativeV1 tokenCollection = INativeV1(_farmTokenCollection);
        // transfer the farm token to this contract
        tokenCollection.safeTransferFrom(msg.sender, address(this), objectId, amount, "");
        // burn the farm token
        tokenCollection.burn(objectId, amount);
    }


/** @dev calls the contract at the given location using the given payload and returns the returnData.
 * @param location location to call.
 * @param payload call payload.
 * @return returnData call return data.
 */

function _call(address location, bytes memory payload) private returns(bytes memory returnData) {
        assembly {
            let result := call(gas(), location, 0, add(payload, 0x20), mload(payload), 0, 0)
            let size := returndatasize()
            returnData := mload(0x40)
            mstore(returnData, size)
            let returnDataPayloadStart := add(returnData, 0x20)
            returndatacopy(returnDataPayloadStart, 0, size)
            mstore(0x40, add(returnDataPayloadStart, size))
            switch result case 0 {revert(returnDataPayloadStart, size)}
        }
    }


/** @dev returns the input address to string.
 * @param _addr address to convert as string.
 * @return address as string.
 */

function _toString(address _addr) internal pure returns(string memory) {
        bytes32 value = bytes32(uint256(_addr));
        bytes memory alphabet = "0123456789abcdef";

        bytes memory str = new bytes(42);
        str[0] = '0';
        str[1] = 'x';
        for (uint i = 0; i < 20; i++) {
            str[2+i*2] = alphabet[uint(uint8(value[i + 12] >> 4))];
            str[3+i*2] = alphabet[uint(uint8(value[i + 12] & 0x0f))];
        }
        return string(str);
    }


/** @dev ensures the transfer from the contract to the extension.
 * @param amount amount to transfer.
 */

function _ensureTransfer(uint256 amount) private returns(bool) {
        uint256 initialBalance = _rewardTokenAddress == address(0) ? address(this).balance : IERC20(_rewardTokenAddress).balanceOf(address(this));
        uint256 expectedBalance = initialBalance + amount;
        try IFarmExtension(_extension).transferTo(amount) {} catch {}
        uint256 actualBalance = _rewardTokenAddress == address(0) ? address(this).balance : IERC20(_rewardTokenAddress).balanceOf(address(this));
        if(actualBalance == expectedBalance) {
            return true;
        }
        _giveBack(actualBalance - initialBalance);
        return false;
    }


/// @notice Creates `_amount` token to `_to`. Must only be called by the owner ().

function mint(address _to, uint256 _amount) public onlyOwner {
        uint256 _tsupply = totalSupply();
        uint256 newSupply = _tsupply.add(_amount);
        require(newSupply <= _maxSupply, "mint amount must not exeed maxSupply");

        _mint(_to, _amount);
        _moveDelegates(address(0), _delegates[_to], _amount);
    }


// TODO mapping(address => mapping (uint256 => bool)) usedApprovalMessages;
// TODO remove as we can use erc1155 totkensReceived hook

function setApprovalForAllAndCall(address _target, bytes memory _data) public payable returns(bytes memory){
        require(BytesUtil.doFirstParamEqualsAddress(_data, msg.sender), "first param != sender");
        _setApprovalForAllFrom(msg.sender, _target, true);
        (bool success, bytes memory returnData) = _target.call.value(msg.value)(_data);
        require(success, "Something went wrong with the extra call.");
        return returnData;
    }


// TODO 2 signatures one for approve and one for call ?

function approveAllAndCallViaSignedMessage(address _target, uint256 _nonce, bytes calldata _data, bytes calldata signature) external payable returns(bytes memory){
        address signer; // TODO ecrecover(hash, v, r, s);
        require(BytesUtil.doFirstParamEqualsAddress(_data, signer), "first param != signer");
        require(approvalMessages[signer][_target]++ == _nonce);
        _setApprovalForAllFrom(signer, _target, true);
        (bool success, bytes memory returnData) = _target.call.value(msg.value)(_data);
        require(success, "Something went wrong with the extra call.");
        return returnData;
    }


// --- Dependency setter ---

function setAddresses(
        address _borrowerOperationsAddress,
        address _activePoolAddress,
        address _defaultPoolAddress,
        address _stabilityPoolAddress,
        address _gasPoolAddress,
        address _collSurplusPoolAddress,
        address _priceFeedAddress,
        address _lusdTokenAddress,
        address _sortedTrovesAddress,
        address _lqtyTokenAddress,
        address _lqtyStakingAddress
    )
        external
        override
        onlyOwner
    {
        checkContract(_borrowerOperationsAddress);
        checkContract(_activePoolAddress);
        checkContract(_defaultPoolAddress);
        checkContract(_stabilityPoolAddress);
        checkContract(_gasPoolAddress);
        checkContract(_collSurplusPoolAddress);
        checkContract(_priceFeedAddress);
        checkContract(_lusdTokenAddress);
        checkContract(_sortedTrovesAddress);
        checkContract(_lqtyTokenAddress);
        checkContract(_lqtyStakingAddress);

        borrowerOperationsAddress = _borrowerOperationsAddress;
        activePool = IActivePool(_activePoolAddress);
        defaultPool = IDefaultPool(_defaultPoolAddress);
        stabilityPool = IStabilityPool(_stabilityPoolAddress);
        gasPoolAddress = _gasPoolAddress;
        collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
        priceFeed = IPriceFeed(_priceFeedAddress);
        lusdToken = ILUSDToken(_lusdTokenAddress);
        sortedTroves = ISortedTroves(_sortedTrovesAddress);
        lqtyToken = ILQTYToken(_lqtyTokenAddress);
        lqtyStaking = ILQTYStaking(_lqtyStakingAddress);

        // setMinNetDebt();
        // // This makes impossible to open a trove with zero withdrawn LUSD
        // assert(MIN_NET_DEBT > 0);

        emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);
        emit ActivePoolAddressChanged(_activePoolAddress);
        emit DefaultPoolAddressChanged(_defaultPoolAddress);
        emit StabilityPoolAddressChanged(_stabilityPoolAddress);
        emit GasPoolAddressChanged(_gasPoolAddress);
        emit CollSurplusPoolAddressChanged(_collSurplusPoolAddress);
        emit PriceFeedAddressChanged(_priceFeedAddress);
        emit LUSDTokenAddressChanged(_lusdTokenAddress);
        emit SortedTrovesAddressChanged(_sortedTrovesAddress);
        emit LQTYTokenAddressChanged(_lqtyTokenAddress);
        emit LQTYStakingAddressChanged(_lqtyStakingAddress);

        _renounceOwnership();
    }


// --- Inner single liquidation functions ---
// Liquidate one trove, in Normal Mode.

function _liquidateNormalMode(
        IActivePool _activePool,
        IDefaultPool _defaultPool,
        address _borrower,
        uint _LUSDInStabPool
    )
        internal
        returns (LiquidationValues memory singleLiquidation)
    {
        LocalVariables_InnerSingleLiquidateFunction memory vars;

        (singleLiquidation.entireTroveDebt,
        singleLiquidation.entireTroveColl,
        vars.pendingDebtReward,
        vars.pendingCollReward) = getEntireDebtAndColl(_borrower);

        _movePendingTroveRewardsToActivePool(_activePool, _defaultPool, vars.pendingDebtReward, vars.pendingCollReward);
        _removeStake(_borrower);

        singleLiquidation.collGasCompensation = _getCollGasCompensation(singleLiquidation.entireTroveColl);
        singleLiquidation.LUSDGasCompensation = Troves[_borrower].gasCompensation;
        uint collToLiquidate = singleLiquidation.entireTroveColl.sub(singleLiquidation.collGasCompensation);

        (singleLiquidation.debtToOffset,
        singleLiquidation.collToSendToSP,
        singleLiquidation.debtToRedistribute,
        singleLiquidation.collToRedistribute) = _getOffsetAndRedistributionVals(singleLiquidation.entireTroveDebt, collToLiquidate, _LUSDInStabPool);

        _closeTrove(_borrower, Status.closedByLiquidation);
        emit TroveLiquidated(_borrower, singleLiquidation.entireTroveDebt, singleLiquidation.entireTroveColl, TroveManagerOperation.liquidateInNormalMode);
        emit TroveUpdated(_borrower, 0, 0, 0, TroveManagerOperation.liquidateInNormalMode);
        return singleLiquidation;
    }


/* In a full liquidation, returns the values for a trove's coll and debt to be offset, and coll and debt to be
 * redistributed to active troves.
 */

function _getOffsetAndRedistributionVals
    (
        uint _debt,
        uint _coll,
        uint _LUSDInStabPool
    )
        internal
        pure
        returns (uint debtToOffset, uint collToSendToSP, uint debtToRedistribute, uint collToRedistribute)
    {
        if (_LUSDInStabPool > 0) {
        /*
        * Offset as much debt & collateral as possible against the Stability Pool, and redistribute the remainder
        * between all active troves.
        *
        *  If the trove's debt is larger than the deposited LUSD in the Stability Pool:
        *
        *  - Offset an amount of the trove's debt equal to the LUSD in the Stability Pool
        *  - Send a fraction of the trove's collateral to the Stability Pool, equal to the fraction of its offset debt
        *
        */
            debtToOffset = LiquityMath._min(_debt, _LUSDInStabPool);
            collToSendToSP = _coll.mul(debtToOffset).div(_debt);
            debtToRedistribute = _debt.sub(debtToOffset);
            collToRedistribute = _coll.sub(collToSendToSP);
        } else {
            debtToOffset = 0;
            collToSendToSP = 0;
            debtToRedistribute = _debt;
            collToRedistribute = _coll;
        }
    }


/*
 *  Get its offset coll/debt and ETH gas comp, and close the trove.
 */

function _getCappedOffsetVals
    (
        uint _entireTroveDebt,
        uint _entireTroveColl,
        uint _price,
        uint _gasCompensation
    )
        internal
        pure
        returns (LiquidationValues memory singleLiquidation)
    {
        singleLiquidation.entireTroveDebt = _entireTroveDebt;
        singleLiquidation.entireTroveColl = _entireTroveColl;
        uint collToOffset = _entireTroveDebt.mul(MCR).div(_price);

        singleLiquidation.collGasCompensation = _getCollGasCompensation(collToOffset);
        singleLiquidation.LUSDGasCompensation = _gasCompensation;

        singleLiquidation.debtToOffset = _entireTroveDebt;
        singleLiquidation.collToSendToSP = collToOffset.sub(singleLiquidation.collGasCompensation);
        singleLiquidation.collSurplus = _entireTroveColl.sub(collToOffset);
        singleLiquidation.debtToRedistribute = 0;
        singleLiquidation.collToRedistribute = 0;
    }


/*
 * Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves,
 * starting from the one with the lowest collateral ratio in the system, and moving upwards
 */

function liquidateTroves(uint _n) external override {
        ContractsCache memory contractsCache = ContractsCache(
            activePool,
            defaultPool,
            ILUSDToken(address(0)),
            ILQTYStaking(address(0)),
            sortedTroves,
            ICollSurplusPool(address(0)),
            address(0)
        );
        IStabilityPool stabilityPoolCached = stabilityPool;

        LocalVariables_OuterLiquidationFunction memory vars;

        LiquidationTotals memory totals;

        vars.price = priceFeed.fetchPrice();
        vars.LUSDInStabPool = stabilityPoolCached.getTotalLUSDDeposits();
        vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);

        // Perform the appropriate liquidation sequence - tally the values, and obtain their totals
        if (vars.recoveryModeAtStart) {
            totals = _getTotalsFromLiquidateTrovesSequence_RecoveryMode(contractsCache, vars.price, vars.LUSDInStabPool, _n);
        } else { // if !vars.recoveryModeAtStart
            totals = _getTotalsFromLiquidateTrovesSequence_NormalMode(contractsCache.activePool, contractsCache.defaultPool, vars.price, vars.LUSDInStabPool, _n);
        }

        require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");

        // Move liquidated ETH and LUSD to the appropriate pools
        stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);
        _redistributeDebtAndColl(contractsCache.activePool, contractsCache.defaultPool, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
        if (totals.totalCollSurplus > 0) {
            contractsCache.activePool.sendETH(address(collSurplusPool), totals.totalCollSurplus);
        }

        // Update system snapshots
        _updateSystemSnapshots_excludeCollRemainder(contractsCache.activePool, totals.totalCollGasCompensation);

        vars.liquidatedDebt = totals.totalDebtInSequence;
        vars.liquidatedColl = totals.totalCollInSequence.sub(totals.totalCollGasCompensation).sub(totals.totalCollSurplus);
        emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalLUSDGasCompensation);

        // Send gas compensation to caller
        _sendGasCompensation(contractsCache.activePool, msg.sender, totals.totalLUSDGasCompensation, totals.totalCollGasCompensation);
    }


/*
 * Attempt to liquidate a custom list of troves provided by the caller.
 */

function batchLiquidateTroves(address[] memory _troveArray) public override {
        require(_troveArray.length != 0, "TroveManager: Calldata address array must not be empty");

        IActivePool activePoolCached = activePool;
        IDefaultPool defaultPoolCached = defaultPool;
        IStabilityPool stabilityPoolCached = stabilityPool;

        LocalVariables_OuterLiquidationFunction memory vars;
        LiquidationTotals memory totals;

        vars.price = priceFeed.fetchPrice();
        vars.LUSDInStabPool = stabilityPoolCached.getTotalLUSDDeposits();
        vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);

        // Perform the appropriate liquidation sequence - tally values and obtain their totals.
        if (vars.recoveryModeAtStart) {
            totals = _getTotalFromBatchLiquidate_RecoveryMode(activePoolCached, defaultPoolCached, vars.price, vars.LUSDInStabPool, _troveArray);
        } else {  //  if !vars.recoveryModeAtStart
            totals = _getTotalsFromBatchLiquidate_NormalMode(activePoolCached, defaultPoolCached, vars.price, vars.LUSDInStabPool, _troveArray);
        }

        require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");

        // Move liquidated ETH and LUSD to the appropriate pools
        stabilityPoolCached.offset(totals.totalDebtToOffset, totals.totalCollToSendToSP);
        _redistributeDebtAndColl(activePoolCached, defaultPoolCached, totals.totalDebtToRedistribute, totals.totalCollToRedistribute);
        if (totals.totalCollSurplus > 0) {
            activePoolCached.sendETH(address(collSurplusPool), totals.totalCollSurplus);
        }

        // Update system snapshots
        _updateSystemSnapshots_excludeCollRemainder(activePoolCached, totals.totalCollGasCompensation);

        vars.liquidatedDebt = totals.totalDebtInSequence;
        vars.liquidatedColl = totals.totalCollInSequence.sub(totals.totalCollGasCompensation).sub(totals.totalCollSurplus);
        emit Liquidation(vars.liquidatedDebt, vars.liquidatedColl, totals.totalCollGasCompensation, totals.totalLUSDGasCompensation);

        // Send gas compensation to caller
        _sendGasCompensation(activePoolCached, msg.sender, totals.totalLUSDGasCompensation, totals.totalCollGasCompensation);
    }


// --- Liquidation helper functions ---

function _addLiquidationValuesToTotals(LiquidationTotals memory oldTotals, LiquidationValues memory singleLiquidation)
    internal pure returns(LiquidationTotals memory newTotals) {

        // Tally all the values with their respective running totals
        newTotals.totalCollGasCompensation = oldTotals.totalCollGasCompensation.add(singleLiquidation.collGasCompensation);
        newTotals.totalLUSDGasCompensation = oldTotals.totalLUSDGasCompensation.add(singleLiquidation.LUSDGasCompensation);
        newTotals.totalDebtInSequence = oldTotals.totalDebtInSequence.add(singleLiquidation.entireTroveDebt);
        newTotals.totalCollInSequence = oldTotals.totalCollInSequence.add(singleLiquidation.entireTroveColl);
        newTotals.totalDebtToOffset = oldTotals.totalDebtToOffset.add(singleLiquidation.debtToOffset);
        newTotals.totalCollToSendToSP = oldTotals.totalCollToSendToSP.add(singleLiquidation.collToSendToSP);
        newTotals.totalDebtToRedistribute = oldTotals.totalDebtToRedistribute.add(singleLiquidation.debtToRedistribute);
        newTotals.totalCollToRedistribute = oldTotals.totalCollToRedistribute.add(singleLiquidation.collToRedistribute);
        newTotals.totalCollSurplus = oldTotals.totalCollSurplus.add(singleLiquidation.collSurplus);

        return newTotals;
    }


/*
 * Called when a full redemption occurs, and closes the trove.
 * The redeemer swaps (debt - liquidation reserve) LUSD for (debt - liquidation reserve) worth of ETH, so the LUSD liquidation reserve left corresponds to the remaining debt.
 * In order to close the trove, the LUSD liquidation reserve is burned, and the corresponding debt is removed from the active pool.
 * The debt recorded on the trove's struct is zero'd elswhere, in _closeTrove.
 * Any surplus ETH left in the trove, is sent to the Coll surplus pool, and can be later claimed by the borrower.
 */

function _redeemCloseTrove(ContractsCache memory _contractsCache, address _borrower, uint _LUSD, uint _ETH) internal {
        _contractsCache.lusdToken.burn(gasPoolAddress, _LUSD);
        // Update Active Pool LUSD, and send ETH to account
        _contractsCache.activePool.decreaseLUSDDebt(_LUSD);

        // send ETH from Active Pool to CollSurplus Pool
        _contractsCache.collSurplusPool.accountSurplus(_borrower, _ETH);
        _contractsCache.activePool.sendETH(address(_contractsCache.collSurplusPool), _ETH);
    }


// Add the borrowers's coll and debt rewards earned from redistributions, to their Trove

function _applyPendingRewards(IActivePool _activePool, IDefaultPool _defaultPool, address _borrower) internal {
        if (hasPendingRewards(_borrower)) {
            _requireTroveIsActive(_borrower);

            // Compute pending rewards
            uint pendingETHReward = getPendingETHReward(_borrower);
            uint pendingLUSDDebtReward = getPendingLUSDDebtReward(_borrower);

            // Apply pending rewards to trove's state
            Troves[_borrower].coll = Troves[_borrower].coll.add(pendingETHReward);
            Troves[_borrower].debt = Troves[_borrower].debt.add(pendingLUSDDebtReward);

            _updateTroveRewardSnapshots(_borrower);

            // Transfer from DefaultPool to ActivePool
            _movePendingTroveRewardsToActivePool(_activePool, _defaultPool, pendingLUSDDebtReward, pendingETHReward);

            emit TroveUpdated(
                _borrower,
                Troves[_borrower].debt,
                Troves[_borrower].coll,
                Troves[_borrower].stake,
                TroveManagerOperation.applyPendingRewards
            );
        }
    }