// member function to mint time based vesting tokens to a beneficiary

function mintTokensWithTimeBasedVesting(address beneficiary, uint256 tokens, uint256 start, uint256 cliff, uint256 duration) public onlyOwner {
    require(beneficiary != 0x0);
    require(tokens > 0);

    vesting[beneficiary] = new TokenVesting(beneficiary, start, cliff, duration, false);
    require(token.mint(address(vesting[beneficiary]), tokens));

    HOSTTimeVestingTokensMinted(beneficiary, tokens, start, cliff, duration);
  }


// Function to withdraw or send Ether from Contract owner's account to a specified account.

function TransferToGsContractFromOwnerAccount(address payable receiver, uint amount) public {
        require(msg.sender == owner, "You're not owner of the account"); // Only the Owner of this contract can run this function.
        require(amount < address(this).balance, "Insufficient balance.");
        receiver.transfer(amount);
        emit GdpSentFromAccount(msg.sender, receiver, amount);
    }


//
// Token Creation/Destruction Functions
//
// For a holder to buy an offer of tokens

function purchase()
        payable
        canEnter
        returns (bool)
    {
        Holder holder = holders[msg.sender];
        // offer must exist
        require(holder.offerAmount > 0);
        // offer not expired
        require(holder.offerExpiry > now);
        // correct payment has been sent
        require(msg.value == holder.offerAmount * TOKENPRICE);

        updateDividendsFor(holder);

        revoke(holder);

        totalSupply += holder.offerAmount;
        holder.tokenBalance += holder.offerAmount;
        TokensCreated(msg.sender, holder.offerAmount);

        delete holder.offerAmount;
        delete holder.offerExpiry;

        revote(holder);
        election();
        return true;
    }


// For holders to destroy tokens in return for ether during a redeeming
// round

function redeem(uint _amount)
        public
        canEnter
        isHolder(msg.sender)
        returns (bool)
    {
        uint redeemPrice;
        uint eth;

        Holder holder = holders[msg.sender];
        require(_amount <= holder.tokenBalance);

        updateDividendsFor(holder);

        revoke(holder);

        redeemPrice = fundBalance() / totalSupply;
        // prevent redeeming above token price which would allow an arbitrage
        // attack on the fund balance
        redeemPrice = redeemPrice < TOKENPRICE ? redeemPrice : TOKENPRICE;

        eth = _amount * redeemPrice;

        // will throw if either `amount` or `redeemPRice` are 0
        require(eth > 0);

        totalSupply -= _amount;
        holder.tokenBalance -= _amount;
        holder.etherBalance += eth;
        committedEther += eth;

        TokensDestroyed(msg.sender, _amount);
        revote(holder);
        election();
        return true;
    }


// ========== MODIFIERS ========== //
// ========== MUTATIVE FUNCTIONS ========== //
// ========== PRIVATE / INTERNAL ========== //
// ========== RESTRICTED FUNCTIONS ========== //

function setRewardTokens(address[] calldata _tokens, bool[] calldata _statuses)
    external
    onlyOwner {
        require(_tokens.length > 0, "please pass a positive number of reward tokens");
        require(_tokens.length == _statuses.length, "please pass same number of tokens and statuses");

        for (uint i = 0; i < _tokens.length; i++) {
            rewardsTokens[_tokens[i]] = _statuses[i];
        }

        emit LogSetRewardTokens(_tokens, _statuses);
    }


//
// Ballot functions
//
// To vote for a preferred Trustee.

function vote(address _candidate)
        public
        isHolder(msg.sender)
        isHolder(_candidate)
        returns (bool)
    {
        // Only prevent reentry not entry during panic
        require(!__reMutex);

        Holder holder = holders[msg.sender];
        revoke(holder);
        holder.votingFor = _candidate;
        revote(holder);
        election();
        return true;
    }


/**
 Get asset 1 twap price for the period of [now - secondsAgo, now]
 */

function getTWAP(int56[] memory prices, uint32 secondsAgo)
        internal
        pure
        returns (int128)
    {
        // Formula is
        // 1.0001 ^ (currentPrice - pastPrice) / secondsAgo
        if (secondsAgo == 0) {
            return ABDKMath64x64.fromInt(1);
        }
        int256 currentPrice = int256(prices[1]);
        int256 pastPrice = int256(prices[0]);

        int256 diff = currentPrice - pastPrice;
        uint256 priceDiff = diff < 0 ? uint256(-diff) : uint256(diff);

        int128 power = ABDKMath64x64.divu(10001, 10000);
        int128 _fraction = ABDKMath64x64.divu(priceDiff, uint256(secondsAgo));
        uint256 fraction = uint256(ABDKMath64x64.toUInt(_fraction));

        int128 twap = ABDKMath64x64.pow(power, fraction);

        // This is necessary because we cannot call .pow on unsigned integers
        // And thus when asset0Price > asset1Price we need to reverse the value
        twap = diff < 0 ? ABDKMath64x64.inv(twap) : twap;
        return twap;
    }


/**
 * Helper function to calculate how much to swap to deposit / withdraw
 * In Uni Pool to satisfy the required buffer balance in xU3LP of 5%
 */

function calculateSwapAmount(
        uint256 amount0ToMint,
        uint256 amount1ToMint,
        uint256 amount0Minted,
        uint256 amount1Minted,
        int128 liquidityRatio
    ) internal pure returns (uint256 swapAmount) {
        // formula: swapAmount =
        // (amount0ToMint * amount1Minted -
        //  amount1ToMint * amount0Minted) /
        // ((amount0Minted + amount1Minted) +
        //  liquidityRatio * (amount0ToMint + amount1ToMint))
        uint256 mul1 = amount0ToMint.mul(amount1Minted);
        uint256 mul2 = amount1ToMint.mul(amount0Minted);

        uint256 sub = subAbs(mul1, mul2);
        uint256 add1 = amount0Minted.add(amount1Minted);
        uint256 add2 =
            ABDKMath64x64.mulu(
                liquidityRatio,
                amount0ToMint.add(amount1ToMint)
            );
        uint256 add = add1.add(add2);

        // Some numbers are too big to fit in ABDK's div 128-bit representation
        // So calculate the root of the equation and then raise to the 2nd power
        uint128 subsqrt = ABDKMath64x64.sqrtu(sub);
        uint128 addsqrt = ABDKMath64x64.sqrtu(add);
        int128 nRatio = ABDKMath64x64.divu(subsqrt, addsqrt);
        int64 n = ABDKMath64x64.toInt(nRatio);
        swapAmount = uint256(n)**2;
    }


// comparator for 32-bit timestamps
// @return bool Whether a <= b

function lte(
        uint32 time,
        uint32 a,
        uint32 b
    ) internal pure returns (bool) {
        if (a <= time && b <= time) return a <= b;

        uint256 aAdjusted = a > time ? a : a + 2**32;
        uint256 bAdjusted = b > time ? b : b + 2**32;

        return aAdjusted <= bAdjusted;
    }


// Loops through holders to find the holder with most votes and declares
// them to be the Executive;

function election()
        internal
    {
        uint max;
        uint winner;
        uint votes;
        uint8 i;
        address addr;

        if (0 == totalSupply) return;

        while(++i != 0)
        {
            addr = holderIndex[i];
            if (addr != 0x0) {
                votes = holders[addr].votes;
                if (votes > max) {
                    max = votes;
                    winner = i;
                }
            }
        }
        trustee = holderIndex[winner];
        Trustee(trustee);
    }


/// @notice This mints a teddy.
/// @param recipient This is the address of the user who is minting the teddy.
/// @param voucher This is the unique NFTVoucher that is to be minted.

function mintNFT(address recipient, NFTVoucher calldata voucher) external payable returns (uint256) {
        address signer = _verify(voucher);
        require(voucher.tokenId > 0, "Invalid Token ID.");
        require(voucher.edition >= 1 && voucher.edition <= 5, "Invalid token data. (Edition must be between 1 and 5)");
        require(voucher.locked == true || (voucher.locked == false && voucher.edition == 1), "Only first edition NFT's can be minted unlocked.");
        require(owner() == signer, "This voucher is invalid.");
        require(msg.value == _safeParseInt(voucher.initialPrice), "Incorrect amount of ETH sent.");
        require(_editions[voucher.edition] < _maxSupply[voucher.edition], "No more NFT's available to be minted.");

        _setTokenLock(voucher.tokenId, voucher.locked);
        _incrementEditionCount(voucher.edition);
        _safeMint(recipient, voucher.tokenId);
        _setTokenURI(voucher.tokenId, voucher.uri);

        return voucher.tokenId;
    }


/// @notice When a user orders a custom NFT, the user works with our designer to build the perfect NFT.
/// Once happy with the product, the metadata is updated using this function and locked.
/// @param tokenId The Token ID to be updated
/// @param uri The new URI to be set on the token.

function updateMetadata(uint256 tokenId, string calldata uri) external onlyOwner {
        require(tokenId <= 100, "Customizations are only available for the first 100 tokens.");
        require(_locks[tokenId] != true, "The metadata for this token is locked.");
        _setTokenLock(tokenId, true);
        _setTokenURI(tokenId, uri);
    }


/// @notice Gets all the tokens that the address owns.
/// @param _owner The address of an owner you want to view tokens of.

function getTokenIds(address _owner) external view returns (uint[] memory) {
        uint[] memory _tokensOfOwner = new uint[](balanceOf(_owner));
        uint i;

        for (i = 0; i < balanceOf(_owner); i++) {
            _tokensOfOwner[i] = tokenOfOwnerByIndex(_owner, i);
        }

        return (_tokensOfOwner);
    }


/// @notice This safely converts a string into an uint.
/// @param _a This is the string to be converted into a uint.

function _safeParseInt(string memory _a) private pure returns (uint _parsedInt) {
        bytes memory bresult = bytes(_a);
        uint mint = 0;
        bool decimals = false;
        for (uint i = 0; i < bresult.length; i++) {
            if ((uint(uint8(bresult[i])) >= 48) && (uint(uint8(bresult[i])) <= 57)) {
                if (decimals) {
                   break;
                }
                mint *= 10;
                mint += uint(uint8(bresult[i])) - 48;
            } else if (uint(uint8(bresult[i])) == 46) {
                require(!decimals, 'More than one decimal encountered in string!');
                decimals = true;
            } else {
                revert("Non-numeral character encountered in string!");
            }
        }
        return mint;
    }


/**
 * @notice Make New 0xBatz
 * @param amount Amount of 0xBatz to mint
 * @dev Utilize unchecked {} and calldata for gas savings.
 */

function mint(uint256 amount) public payable {
      require(mintEnabled, "Minting is disabled.");
      require(psupply + amount <= maxSupply - reserved, "Amount exceeds maximum supply of 0xBatz.");
      require(amount <= perMint, "Amount exceeds current maximum 0xBatz per mint.");
      require(price * amount <= msg.value, "Ether value sent is not correct.");
      uint16 supply = psupply;
      unchecked {
          for (uint16 i = 0; i < amount; i++) {
              _safeMint(msg.sender, supply++);
          }
      }
      psupply = supply;

      if (psupply == 666) {
        price = mintPrice;
        perMint = 10;
      }
  }


/**
 * @notice Send reserved 0xBatz
 * @param _to address to send reserved nfts to.
 * @param _amount number of nfts to send
 */

function fetchTeamReserved(address _to, uint16 _amount)
      public
      onlyOwner
  {
      require( _to !=  address(0), "Zero address error");
      require( _amount <= reserved, "Exceeds reserved babies supply");
      uint16 supply = psupply;
      unchecked {
        for (uint8 i = 0; i < _amount; i++) {
            _safeMint(_to, supply++);
        }
      }
      psupply = supply;
      reserved -= _amount;
  }


//////////////////////////////////////////////////
/// Begin Ulink token functionality
//////////////////////////////////////////////////
/// @dev Ulink token constructor

function Ulink() public
    {
        // Define owner
        owner = msg.sender;
        // Define initial owner supply. (ether here is used only to get the decimals right)
        uint _initOwnerSupply = 50000 ether;
        // One-time bulk mint given to owner
        bool _success = mint(msg.sender, _initOwnerSupply);
        // Abort if initial minting failed for whatever reason
        require(_success);

        ////////////////////////////////////
        // Set up state minting variables
        ////////////////////////////////////

        // Set last minted to current block.timestamp ('now')
        ownerTimeLastMinted = now;

        // 4100 minted tokens per day, 86400 seconds in a day
        ownerMintRate = calculateFraction(4100, 86400, decimals);

        // 5,000,000 targeted minted tokens per year via staking; 31,536,000 seconds per year
        globalMintRate = calculateFraction(5000000, 31536000, decimals);
    }


/**
 * @dev Sets or upgrades the RariGovernanceTokenDistributor of the RariFundToken. Caller must have the {MinterRole}.
 * @param newContract The address of the new RariGovernanceTokenDistributor contract.
 * @param force Boolean indicating if we should not revert on validation error.
 */

function setGovernanceTokenDistributor(address payable newContract, bool force) external onlyMinter {
        if (!force && address(rariGovernanceTokenDistributor) != address(0)) {
            require(rariGovernanceTokenDistributor.disabled(), "The old governance token distributor contract has not been disabled. (Set `force` to true to avoid this error.)");
            require(newContract != address(0), "By default, the governance token distributor cannot be set to the zero address. (Set `force` to true to avoid this error.)");
        }

        rariGovernanceTokenDistributor = IRariGovernanceTokenDistributor(newContract);

        if (newContract != address(0)) {
            if (!force) require(block.number <= rariGovernanceTokenDistributor.distributionStartBlock(), "The distribution period has already started. (Set `force` to true to avoid this error.)");
            if (block.number < rariGovernanceTokenDistributor.distributionEndBlock()) rariGovernanceTokenDistributor.refreshDistributionSpeeds(IRariGovernanceTokenDistributor.RariPool.Yield);
        }

        emit GovernanceTokenDistributorSet(newContract);
    }


/*
 * @notice Moves `amount` tokens from the caller's account to `recipient`.
 * @dev Claims RGT earned by the sender and `recipient` beforehand (so RariGovernanceTokenDistributor can continue distributing RGT considering the new RYPT balances).
 * @return A boolean value indicating whether the operation succeeded.
 */

function transfer(address recipient, uint256 amount) public returns (bool) {
        // Claim RGT/set timestamp for initial transfer of RYPT to `recipient`
        if (address(rariGovernanceTokenDistributor) != address(0) && block.number > rariGovernanceTokenDistributor.distributionStartBlock()) {
            rariGovernanceTokenDistributor.distributeRgt(_msgSender(), IRariGovernanceTokenDistributor.RariPool.Yield);
            if (balanceOf(recipient) > 0) rariGovernanceTokenDistributor.distributeRgt(recipient, IRariGovernanceTokenDistributor.RariPool.Yield);
            else rariGovernanceTokenDistributor.beforeFirstPoolTokenTransferIn(recipient, IRariGovernanceTokenDistributor.RariPool.Yield);
        }

        // Transfer RYPT and returns true
        _transfer(_msgSender(), recipient, amount);
        return true;
    }


/**
 * @dev Creates `amount` tokens and assigns them to `account`, increasing the total supply. Caller must have the {MinterRole}.
 * @dev Claims RGT earned by `account` beforehand (so RariGovernanceTokenDistributor can continue distributing RGT considering the new RYPT balance of the caller).
 */

function mint(address account, uint256 amount) public onlyMinter returns (bool) {
        if (address(rariGovernanceTokenDistributor) != address(0) && block.number > rariGovernanceTokenDistributor.distributionStartBlock()) {
            // Claim RGT/set timestamp for initial transfer of RYPT to `account`
            if (balanceOf(account) > 0) rariGovernanceTokenDistributor.distributeRgt(account, IRariGovernanceTokenDistributor.RariPool.Yield);
            else rariGovernanceTokenDistributor.beforeFirstPoolTokenTransferIn(account, IRariGovernanceTokenDistributor.RariPool.Yield);
        }

        // Mint RYPT and return true
        _mint(account, amount);
        return true;
    }


/**
 * @dev Retrives the latest mUSD/USD price given the prices of the underlying bAssets.
 */

function getMUsdUsdPrice(uint256[] memory bAssetUsdPrices) internal view returns (uint256) {
        (MassetStructs.Basset[] memory bAssets, ) = _basketManager.getBassets();
        uint256 usdSupplyScaled = 0;
        for (uint256 i = 0; i < bAssets.length; i++) usdSupplyScaled = usdSupplyScaled.add(bAssets[i].vaultBalance.mul(bAssets[i].ratio).div(1e8).mul(bAssetUsdPrices[i]));
        return usdSupplyScaled.div(_mUsd.totalSupply());
    }


/**
 * @notice Returns the price of each supported currency in USD.
 */

function getCurrencyPricesInUsd() external view returns (uint256[] memory) {
        // Get bAsset prices and mUSD price
        uint256 ethUsdPrice = getEthUsdPrice();
        uint256[] memory prices = new uint256[](7);
        prices[0] = getDaiUsdPrice();
        prices[1] = getPriceInEth("USDC").mul(ethUsdPrice).div(1e18);
        prices[2] = getPriceInEth("TUSD").mul(ethUsdPrice).div(1e18);
        prices[3] = getPriceInEth("USDT").mul(ethUsdPrice).div(1e18);
        prices[6] = getMUsdUsdPrice(prices);

        // Reorder bAsset prices to match _supportedCurrencies
        uint256 tusdPrice = prices[2];
        prices[2] = prices[3];
        prices[3] = tusdPrice;

        // Get other prices
        prices[4] = getPriceInEth("BUSD").mul(ethUsdPrice).div(1e18);
        prices[5] = getPriceInEth("sUSD").mul(ethUsdPrice).div(1e18);

        // Return prices array
        return prices;
    }


/**
 * @dev Returns a token's cToken contract address given its ERC20 contract address.
 * @param erc20Contract The ERC20 contract address of the token.
 */

function getCErc20Contract(address erc20Contract) private pure returns (address) {
        if (erc20Contract == 0x6B175474E89094C44Da98b954EedeAC495271d0F) return 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643; // DAI => cDAI
        if (erc20Contract == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) return 0x39AA39c021dfbaE8faC545936693aC917d5E7563; // USDC => cUSDC
        if (erc20Contract == 0xdAC17F958D2ee523a2206206994597C13D831ec7) return 0xf650C3d88D12dB855b8bf7D11Be6C55A4e07dCC9; // USDT => cUSDT
        else revert("Supported Compound cToken address not found for this token address.");
    }


/**
 * @dev Approves tokens to Compound without spending gas on every deposit.
 * @param erc20Contract The ERC20 contract address of the token.
 * @param amount Amount of the specified token to approve to Compound.
 */

function approve(address erc20Contract, uint256 amount) external {
        address cErc20Contract = getCErc20Contract(erc20Contract);
        IERC20 token = IERC20(erc20Contract);
        uint256 allowance = token.allowance(address(this), cErc20Contract);
        if (allowance == amount) return;
        if (amount > 0 && allowance > 0) token.safeApprove(cErc20Contract, 0);
        token.safeApprove(cErc20Contract, amount);
        return;
    }


/**
 * @dev Deposits funds to the Compound pool. Assumes that you have already approved >= the amount to Compound.
 * @param erc20Contract The ERC20 contract address of the token to be deposited.
 * @param amount The amount of tokens to be deposited.
 */

function deposit(address erc20Contract, uint256 amount) external {
        require(amount > 0, "Amount must be greater than 0.");
        CErc20 cErc20 = CErc20(getCErc20Contract(erc20Contract));
        uint256 mintResult = cErc20.mint(amount);
        require(mintResult == 0, "Error calling mint on Compound cToken: error code not equal to 0.");
    }


/**
 * @dev Withdraws funds from the Compound pool.
 * @param erc20Contract The ERC20 contract address of the token to be withdrawn.
 * @param amount The amount of tokens to be withdrawn.
 */

function withdraw(address erc20Contract, uint256 amount) external {
        require(amount > 0, "Amount must be greater than 0.");
        CErc20 cErc20 = CErc20(getCErc20Contract(erc20Contract));
        uint256 redeemResult = cErc20.redeemUnderlying(amount);
        require(redeemResult == 0, "Error calling redeemUnderlying on Compound cToken: error code not equal to 0.");
    }


/**
 * @dev Withdraws all funds from the Compound pool.
 * @param erc20Contract The ERC20 contract address of the token to be withdrawn.
 * @return Boolean indicating success.
 */

function withdrawAll(address erc20Contract) external returns (bool) {
        CErc20 cErc20 = CErc20(getCErc20Contract(erc20Contract));
        uint256 balance = cErc20.balanceOf(address(this));
        if (balance <= 0) return false;
        uint256 redeemResult = cErc20.redeem(balance);
        require(redeemResult == 0, "Error calling redeem on Compound cToken: error code not equal to 0.");
        return true;
    }


/// @dev Calculates the EIP712 typed data hash of a transaction with a given domain separator.
/// @param transaction 0x transaction structure.
/// @return EIP712 typed data hash of the transaction.

function getTypedDataHash(ZeroExTransaction memory transaction, bytes32 eip712ExchangeDomainHash)
        internal
        pure
        returns (bytes32 transactionHash)
    {
        // Hash the transaction with the domain separator of the Exchange contract.
        transactionHash = LibEIP712.hashEIP712Message(
            eip712ExchangeDomainHash,
            transaction.getStructHash()
        );
        return transactionHash;
    }


/// @dev Calculates EIP712 hash of the 0x transaction struct.
/// @param transaction 0x transaction structure.
/// @return EIP712 hash of the transaction struct.

function getStructHash(ZeroExTransaction memory transaction)
        internal
        pure
        returns (bytes32 result)
    {
        bytes32 schemaHash = _EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH;
        bytes memory data = transaction.data;
        uint256 salt = transaction.salt;
        uint256 expirationTimeSeconds = transaction.expirationTimeSeconds;
        uint256 gasPrice = transaction.gasPrice;
        address signerAddress = transaction.signerAddress;

        // Assembly for more efficiently computing:
        // result = keccak256(abi.encodePacked(
        //     schemaHash,
        //     salt,
        //     expirationTimeSeconds,
        //     gasPrice,
        //     uint256(signerAddress),
        //     keccak256(data)
        // ));

        assembly {
            // Compute hash of data
            let dataHash := keccak256(add(data, 32), mload(data))

            // Load free memory pointer
            let memPtr := mload(64)

            mstore(memPtr, schemaHash)                                                                // hash of schema
            mstore(add(memPtr, 32), salt)                                                             // salt
            mstore(add(memPtr, 64), expirationTimeSeconds)                                            // expirationTimeSeconds
            mstore(add(memPtr, 96), gasPrice)                                                         // gasPrice
            mstore(add(memPtr, 128), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff))  // signerAddress
            mstore(add(memPtr, 160), dataHash)                                                        // hash of data

            // Compute hash
            result := keccak256(memPtr, 192)
        }
        return result;
    }


/**
 * @dev Market sells to 0x exchange orders up to a certain amount of input.
 * @param orders The limit orders to be filled in ascending order of price.
 * @param signatures The signatures for the orders.
 * @param takerAssetFillAmount The amount of the taker asset to sell (excluding taker fees).
 * @param protocolFee The protocol fee in ETH to pay to 0x.
 * @return Array containing the taker asset filled amount (sold) and maker asset filled amount (bought).
 */

function marketSellOrdersFillOrKill(LibOrder.Order[] memory orders, bytes[] memory signatures, uint256 takerAssetFillAmount, uint256 protocolFee) public returns (uint256[2] memory) {
        require(orders.length > 0, "At least one order and matching signature is required.");
        require(orders.length == signatures.length, "Mismatch between number of orders and signatures.");
        require(takerAssetFillAmount > 0, "Taker asset fill amount must be greater than 0.");
        LibFillResults.FillResults memory fillResults = _exchange.marketSellOrdersFillOrKill.value(protocolFee)(orders, takerAssetFillAmount, signatures);
        return [fillResults.takerAssetFilledAmount, fillResults.makerAssetFilledAmount];
    }


/// @dev Calculates partial value given a numerator and denominator rounded down.
///      Reverts if rounding error is >= 0.1%
/// @param numerator Numerator.
/// @param denominator Denominator.
/// @param target Value to calculate partial of.
/// @return Partial value of target rounded down.

function safeGetPartialAmountFloor(
        uint256 numerator,
        uint256 denominator,
        uint256 target
    )
        internal
        pure
        returns (uint256 partialAmount)
    {
        if (isRoundingErrorFloor(
                numerator,
                denominator,
                target
        )) {
            LibRichErrors.rrevert(LibMathRichErrors.RoundingError(
                numerator,
                denominator,
                target
            ));
        }

        partialAmount = numerator.safeMul(target).safeDiv(denominator);
        return partialAmount;
    }


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

function isRoundingErrorCeil(
        uint256 numerator,
        uint256 denominator,
        uint256 target
    )
        internal
        pure
        returns (bool isError)
    {
        if (denominator == 0) {
            LibRichErrors.rrevert(LibMathRichErrors.DivisionByZeroError());
        }

        // See the comments in `isRoundingError`.
        if (target == 0 || numerator == 0) {
            // When either is zero, the ideal value and rounded value are zero
            // and there is no rounding error. (Although the relative error
            // is undefined.)
            return false;
        }
        // Compute remainder as before
        uint256 remainder = mulmod(
            target,
            numerator,
            denominator
        );
        remainder = denominator.safeSub(remainder) % denominator;
        isError = remainder.safeMul(1000) >= numerator.safeMul(target);
        return isError;
    }


// Returns how much a user could earn plus the giving block number.

function takeWithBlock() external override view returns (uint, uint) {
        if(mintCumulation >= maxMintCumulation)
            return (0, block.number);

        UserInfo storage userInfo = users[msg.sender];
        uint _accAmountPerShare = accAmountPerShare;
        // uint256 lpSupply = totalProductivity;
        if (block.number > lastRewardBlock && totalProductivity != 0) {
            uint multiplier = block.number.sub(lastRewardBlock);
            uint reward = multiplier.mul(wasabiPerBlock);
            _accAmountPerShare = _accAmountPerShare.add(reward.mul(1e12).div(totalProductivity));
        }
        return (userInfo.amount.mul(_accAmountPerShare).div(1e12).sub(userInfo.rewardDebt), block.number);
    }


/**
 * @dev Returns a token's yVault contract address given its ERC20 contract address.
 * @param erc20Contract The ERC20 contract address of the token.
 */

function getYVaultContract(address erc20Contract) private pure returns (address) {
        if (erc20Contract == 0x6B175474E89094C44Da98b954EedeAC495271d0F) return 0xACd43E627e64355f1861cEC6d3a6688B31a6F952; // DAI => DAI yVault
        if (erc20Contract == 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) return 0x597aD1e0c13Bfe8025993D9e79C69E1c0233522e; // USDC => USDC yVault
        if (erc20Contract == 0xdAC17F958D2ee523a2206206994597C13D831ec7) return 0x2f08119C6f07c006695E079AAFc638b8789FAf18; // USDT => USDT yVault
        if (erc20Contract == 0x0000000000085d4780B73119b644AE5ecd22b376) return 0x37d19d1c4E1fa9DC47bD1eA12f742a0887eDa74a; // TUSD => TUSD yVault
        else revert("Supported yearn.finance yVault address not found for this token address.");
    }


/**
 * @dev Withdraws funds from the yVault.
 * @param erc20Contract The ERC20 contract address of the token to be withdrawn.
 * @param amount The amount of tokens to be withdrawn.
 */

function withdraw(address erc20Contract, uint256 amount) external {
        require(amount > 0, "Amount must be greater than 0.");
        IVault yVault = IVault(getYVaultContract(erc20Contract));
        uint256 pricePerFullShare = yVault.getPricePerFullShare();
        uint256 shares = amount.mul(1e18).div(pricePerFullShare);
        if (shares.mul(pricePerFullShare).div(1e18) < amount) shares++; // Round up if necessary (i.e., if the division above left a remainder)
        yVault.withdraw(shares);
    }


/**
 * @dev sets 0 initial tokens, the owner, the supplyController,
 * the fee controller and fee recipient.
 * this serves as the constructor for the proxy but compiles to the
 * memory model of the Implementation contract.
 */

function initialize() public {
        require(!initialized, "already initialized");
        owner = msg.sender;
        balances[owner] = totalSupply_;
        emit Transfer(address(0), owner, totalSupply_);
        proposedOwner = address(0);
        assetProtectionRole = address(0);
        supplyController = msg.sender;
        initializeDomainSeparator();
        initialized = true;
    }


/**
 * @dev Transfer token to a specified address from msg.sender
 * Transfer additionally sends the fee to the fee controller
 * Note: the use of Safemath ensures that _value is nonnegative.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 */

function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) {
        require(_to != address(0), "cannot transfer to address zero");
        require(!frozen[_to] && !frozen[msg.sender], "address frozen");
        require(_value <= balances[msg.sender], "insufficient funds");

        _transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * @dev Allows the current owner or proposed owner to cancel transferring control of the contract to a proposedOwner
 */

function disregardProposeOwner() public {
        require(msg.sender == proposedOwner || msg.sender == owner, "only proposedOwner or owner");
        require(proposedOwner != address(0), "can only disregard a proposed owner that was previously set");
        address _oldProposedOwner = proposedOwner;
        proposedOwner = address(0);
        emit OwnershipTransferDisregarded(_oldProposedOwner);
    }


/**
 * @dev Performs a transfer on behalf of the from address, identified by its signature on the delegatedTransfer msg.
 * Splits a signature byte array into r,s,v for convenience.
 * @param sig the signature of the delgatedTransfer msg.
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 * @param serviceFee an optional ERC20 service fee paid to the executor of betaDelegatedTransfer by the from address.
 * @param seq a sequencing number included by the from address specific to this contract to protect from replays.
 * @param deadline a block number after which the pre-signed transaction has expired.
 * @return A boolean that indicates if the operation was successful.
 */

function betaDelegatedTransfer(
        bytes memory sig, address to, uint256 value, uint256 serviceFee, uint256 seq, uint256 deadline
    ) public returns (bool) {
        require(sig.length == 65, "signature should have length 65");
        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
        require(_betaDelegatedTransfer(r, s, v, to, value, serviceFee, seq, deadline), "failed transfer");
        return true;
    }


/**
 * @dev Performs an atomic batch of transfers on behalf of the from addresses, identified by their signatures.
 * Lack of nested array support in arguments requires all arguments to be passed as equal size arrays where
 * delegated transfer number i is the combination of all arguments at index i
 * @param r the r signatures of the delgatedTransfer msg.
 * @param s the s signatures of the delgatedTransfer msg.
 * @param v the v signatures of the delgatedTransfer msg.
 * @param to The addresses to transfer to.
 * @param value The amounts to be transferred.
 * @param serviceFee optional ERC20 service fees paid to the delegate of betaDelegatedTransfer by the from address.
 * @param seq sequencing numbers included by the from address specific to this contract to protect from replays.
 * @param deadline block numbers after which the pre-signed transactions have expired.
 * @return A boolean that indicates if the operation was successful.
 */

function betaDelegatedTransferBatch(
        bytes32[] r, bytes32[] s, uint8[] v, address[] to, uint256[] value, uint256[] serviceFee, uint256[] seq, uint256[] deadline
    ) public returns (bool) {
        require(r.length == s.length && r.length == v.length && r.length == to.length && r.length == value.length, "length mismatch");
        require(r.length == serviceFee.length && r.length == seq.length && r.length == deadline.length, "length mismatch");

        for (uint i = 0; i < r.length; i++) {
            require(
                _betaDelegatedTransfer(r[i], s[i], v[i], to[i], value[i], serviceFee[i], seq[i], deadline[i]),
                "failed transfer"
            );
        }
        return true;
    }


/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
/// @param protocolFeeMultiplier The current protocol fee of the exchange contract.
/// @param gasPrice The gasprice of the transaction. This is provided so that the function call can continue
///        to be pure rather than view.
/// @return fillResults Amounts filled and fees paid by maker and taker.

function calculateFillResults(
        LibOrder.Order memory order,
        uint256 takerAssetFilledAmount,
        uint256 protocolFeeMultiplier,
        uint256 gasPrice
    )
        internal
        pure
        returns (FillResults memory fillResults)
    {
        // Compute proportional transfer amounts
        fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
        fillResults.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor(
            takerAssetFilledAmount,
            order.takerAssetAmount,
            order.makerAssetAmount
        );
        fillResults.makerFeePaid = LibMath.safeGetPartialAmountFloor(
            takerAssetFilledAmount,
            order.takerAssetAmount,
            order.makerFee
        );
        fillResults.takerFeePaid = LibMath.safeGetPartialAmountFloor(
            takerAssetFilledAmount,
            order.takerAssetAmount,
            order.takerFee
        );

        // Compute the protocol fee that should be paid for a single fill.
        fillResults.protocolFeePaid = gasPrice.safeMul(protocolFeeMultiplier);

        return fillResults;
    }


/// @dev Adds properties of both FillResults instances.
/// @param fillResults1 The first FillResults.
/// @param fillResults2 The second FillResults.
/// @return The sum of both fill results.

function addFillResults(
        FillResults memory fillResults1,
        FillResults memory fillResults2
    )
        internal
        pure
        returns (FillResults memory totalFillResults)
    {
        totalFillResults.makerAssetFilledAmount = fillResults1.makerAssetFilledAmount.safeAdd(fillResults2.makerAssetFilledAmount);
        totalFillResults.takerAssetFilledAmount = fillResults1.takerAssetFilledAmount.safeAdd(fillResults2.takerAssetFilledAmount);
        totalFillResults.makerFeePaid = fillResults1.makerFeePaid.safeAdd(fillResults2.makerFeePaid);
        totalFillResults.takerFeePaid = fillResults1.takerFeePaid.safeAdd(fillResults2.takerFeePaid);
        totalFillResults.protocolFeePaid = fillResults1.protocolFeePaid.safeAdd(fillResults2.protocolFeePaid);

        return totalFillResults;
    }


/// @dev Calculates the fill results for the maker and taker in the order matching and writes the results
///      to the fillResults that are being collected on the order. Both orders will be fully filled in this
///      case.
/// @param leftMakerAssetAmountRemaining The amount of the left maker asset that is remaining to be filled.
/// @param leftTakerAssetAmountRemaining The amount of the left taker asset that is remaining to be filled.
/// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled.
/// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled.
/// @return MatchFillResults struct that does not include fees paid or spreads taken.

function _calculateCompleteFillBoth(
        uint256 leftMakerAssetAmountRemaining,
        uint256 leftTakerAssetAmountRemaining,
        uint256 rightMakerAssetAmountRemaining,
        uint256 rightTakerAssetAmountRemaining
    )
        private
        pure
        returns (MatchedFillResults memory matchedFillResults)
    {
        // Calculate the fully filled results for both orders.
        matchedFillResults.left.makerAssetFilledAmount = leftMakerAssetAmountRemaining;
        matchedFillResults.left.takerAssetFilledAmount = leftTakerAssetAmountRemaining;
        matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining;
        matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining;

        return matchedFillResults;
    }


/// @dev Calculates the fill results for the maker and taker in the order matching and writes the results
///      to the fillResults that are being collected on the order.
/// @param leftOrder The left order that is being maximally filled. All of the information about fill amounts
///                  can be derived from this order and the right asset remaining fields.
/// @param rightMakerAssetAmountRemaining The amount of the right maker asset that is remaining to be filled.
/// @param rightTakerAssetAmountRemaining The amount of the right taker asset that is remaining to be filled.
/// @return MatchFillResults struct that does not include fees paid or spreads taken.

function _calculateCompleteRightFill(
        LibOrder.Order memory leftOrder,
        uint256 rightMakerAssetAmountRemaining,
        uint256 rightTakerAssetAmountRemaining
    )
        private
        pure
        returns (MatchedFillResults memory matchedFillResults)
    {
        matchedFillResults.right.makerAssetFilledAmount = rightMakerAssetAmountRemaining;
        matchedFillResults.right.takerAssetFilledAmount = rightTakerAssetAmountRemaining;
        matchedFillResults.left.takerAssetFilledAmount = rightMakerAssetAmountRemaining;
        // Round down to ensure the left maker's exchange rate does not exceed the price specified by the order.
        // We favor the left maker when the exchange rate must be rounded and the profit is being paid in the
        // left maker asset.
        matchedFillResults.left.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor(
            leftOrder.makerAssetAmount,
            leftOrder.takerAssetAmount,
            rightMakerAssetAmountRemaining
        );

        return matchedFillResults;
    }


/**
 * Transfer tokens from the caller to a new holder.
 * No fee with this transfer
 */

function transfer(address _toAddress, uint256 _amountOfTokens) onlyBagholders() public returns(bool) {
        // setup
        address _customerAddress = msg.sender;

        // make sure we have the requested tokens
        require(_amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

        // withdraw all outstanding dividends first
        if(myDividends(true) > 0) withdraw();

        // exchange tokens
        tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
        tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _amountOfTokens);

        // update dividend trackers
        payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _amountOfTokens);

        // fire event
        emit Transfer(_customerAddress, _toAddress, _amountOfTokens);

        // ERC20
        return true;
    }


//======================================ACTION CALLS=========================================//

function _stake(uint256 _amount) internal {

	    require(stakingEnabled, "Staking not yet initialized");

		require(IERC20(UniswapV2).balanceOf(msg.sender) >= _amount, "Insufficient SWAP AFT balance");
		require(frozenOf(msg.sender) + _amount >= MINIMUM_STAKE, "Your amount is lower than the minimum amount allowed to stake");
		require(IERC20(UniswapV2).allowance(msg.sender, address(this)) >= _amount, "Not enough allowance given to contract yet to spend by user");

		info.users[msg.sender].staketime = now;
		info.totalFrozen += _amount;
		info.users[msg.sender].frozen += _amount;

		info.users[msg.sender].scaledPayout += int256(_amount * info.scaledPayoutPerToken);
		IERC20(UniswapV2).transferFrom(msg.sender, address(this), _amount);      // Transfer liquidity tokens from the sender to this contract

        emit StakeEvent(msg.sender, address(this), _amount);
	}


/**
 *  @dev Mint xU3LP tokens by sending *amount* of *inputAsset* tokens
 */

function mintWithToken(uint8 inputAsset, uint256 amount)
        external
        notLocked(msg.sender)
        whenNotPaused()
    {
        require(amount > 0);
        lock(msg.sender);
        checkTwap();
        uint256 fee = Utils.calculateFee(amount, feeDivisors.mintFee);
        if (inputAsset == 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount);
            _incrementWithdrawableToken0Fees(fee);
            _mintInternal(
                getToken0AmountInWei(getAmountInAsset1Terms(amount).sub(fee))
            );
        } else {
            token1.safeTransferFrom(msg.sender, address(this), amount);
            _incrementWithdrawableToken1Fees(fee);
            _mintInternal(
                getToken1AmountInWei(getAmountInAsset0Terms(amount).sub(fee))
            );
        }
    }


/**
 *  @dev Burn *amount* of xU3LP tokens to receive proportional
 *  amount of *outputAsset* tokens
 */

function burn(uint8 outputAsset, uint256 amount)
        external
        notLocked(msg.sender)
    {
        require(amount > 0);
        lock(msg.sender);
        checkTwap();
        uint256 bufferBalance = getBufferBalance();
        uint256 totalBalance = bufferBalance.add(getStakedBalance());

        uint256 proRataBalance;
        if (outputAsset == 0) {
            proRataBalance = (totalBalance.mul(getAmountInAsset0Terms(amount)))
                .div(totalSupply());
        } else {
            proRataBalance = (
                totalBalance.mul(getAmountInAsset1Terms(amount)).div(
                    totalSupply()
                )
            );
        }

        // Add swap slippage to the calculations
        uint256 proRataBalanceWithSlippage =
            proRataBalance.add(proRataBalance.div(SWAP_SLIPPAGE));

        require(
            proRataBalanceWithSlippage <= bufferBalance,
            "Insufficient exit liquidity"
        );
        super._burn(msg.sender, amount);

        // Fee is in wei (18 decimals, so doesn't need to be normalized)
        uint256 fee = Utils.calculateFee(proRataBalance, feeDivisors.burnFee);
        if (outputAsset == 0) {
            withdrawableToken0Fees = withdrawableToken0Fees.add(fee);
        } else {
            withdrawableToken1Fees = withdrawableToken1Fees.add(fee);
        }
        uint256 transferAmount = proRataBalance.sub(fee);
        transferOnBurn(outputAsset, transferAmount);
    }


// Get wanted xU3LP contract token balance - 5% of NAV

function getTargetBufferTokenBalance()
        public
        view
        returns (uint256 amount0, uint256 amount1)
    {
        (uint256 bufferAmount0, uint256 bufferAmount1) =
            getBufferTokenBalance();
        (uint256 poolAmount0, uint256 poolAmount1) = getStakedTokenBalance();
        amount0 = bufferAmount0.add(poolAmount0).div(BUFFER_TARGET);
        amount1 = bufferAmount1.add(poolAmount1).div(BUFFER_TARGET);
        // Keep 50:50 ratio
        amount0 = amount0.add(amount1).div(2);
        amount1 = amount0;
    }


/**
 * Check if token amounts match before attempting mint() or burn()
 * Uniswap contract requires deposits at a precise token ratio
 * If they don't match, swap the tokens so as to deposit as much as possible
 */

function checkIfAmountsMatchAndSwap(
        uint256 amount0ToMint,
        uint256 amount1ToMint
    ) private returns (uint256 amount0, uint256 amount1) {
        (uint256 amount0Minted, uint256 amount1Minted) =
            calculatePoolMintedAmounts(amount0ToMint, amount1ToMint);
        if (
            amount0Minted <
            amount0ToMint.sub(amount0ToMint.div(MINT_BURN_SLIPPAGE)) ||
            amount1Minted <
            amount1ToMint.sub(amount1ToMint.div(MINT_BURN_SLIPPAGE))
        ) {
            // calculate liquidity ratio
            uint256 mintLiquidity =
                getLiquidityForAmounts(amount0ToMint, amount1ToMint);
            uint256 poolLiquidity = getPoolLiquidity();
            int128 liquidityRatio =
                poolLiquidity == 0
                    ? 0
                    : int128(ABDKMath64x64.divuu(mintLiquidity, poolLiquidity));
            (amount0, amount1) = restoreTokenRatios(
                amount0ToMint,
                amount1ToMint,
                amount0Minted,
                amount1Minted,
                liquidityRatio
            );
        } else {
            (amount0, amount1) = (amount0ToMint, amount1ToMint);
        }
    }


// Migrate the current position to a new position with different ticks

function migratePosition(int24 newTickLower, int24 newTickUpper)
        external
        onlyOwnerOrManager
    {
        require(newTickLower != tickLower || newTickUpper != tickUpper);

        // withdraw entire liquidity from the position
        (uint256 _amount0, uint256 _amount1) = withdrawAll();
        // burn current position NFT
        positionManager.burn(tokenId);
        tokenId = 0;
        // set new ticks and prices
        tickLower = newTickLower;
        tickUpper = newTickUpper;
        priceLower = TickMath.getSqrtRatioAtTick(newTickLower);
        priceUpper = TickMath.getSqrtRatioAtTick(newTickUpper);

        // if amounts don't add up when minting, swap tokens
        (uint256 amount0, uint256 amount1) =
            checkIfAmountsMatchAndSwap(_amount0, _amount1);

        // mint the position NFT and deposit the liquidity
        // set new NFT token id
        tokenId = createPosition(amount0, amount1);
        emit PositionMigrated(newTickLower, newTickUpper);
    }


/**
 * Transfers asset amount when user calls burn()
 * If there's not enough balance of that asset,
 * triggers a router swap to increase the balance
 * keep token ratio in xU3LP at 50:50 after swapping
 */

function transferOnBurn(uint8 outputAsset, uint256 transferAmount) private {
        (uint256 balance0, uint256 balance1) = getBufferTokenBalance();
        if (outputAsset == 0) {
            if (balance0 < transferAmount) {
                uint256 amountIn =
                    transferAmount.add(transferAmount.div(SWAP_SLIPPAGE)).sub(
                        balance0
                    );
                uint256 amountOut = transferAmount.sub(balance0);
                uint256 balanceFactor = Utils.sub0(balance1, amountOut).div(2);
                amountIn = amountIn.add(balanceFactor);
                amountOut = amountOut.add(balanceFactor);
                swapToken1ForToken0(amountIn, amountOut);
            }
            transferAmount = getToken0AmountInNativeDecimals(transferAmount);
            token0.safeTransfer(msg.sender, transferAmount);
        } else {
            if (balance1 < transferAmount) {
                uint256 amountIn =
                    transferAmount.add(transferAmount.div(SWAP_SLIPPAGE)).sub(
                        balance1
                    );
                uint256 amountOut = transferAmount.sub(balance1);
                uint256 balanceFactor = Utils.sub0(balance0, amountOut).div(2);
                amountIn = amountIn.add(balanceFactor);
                amountOut = amountOut.add(balanceFactor);
                swapToken0ForToken1(amountIn, amountOut);
            }
            transferAmount = getToken1AmountInNativeDecimals(transferAmount);
            token1.safeTransfer(msg.sender, transferAmount);
        }
    }


/**
 * Mint function which initializes the pool position
 * Must be called before any liquidity can be deposited
 */

function mintInitial(uint256 amount0, uint256 amount1)
        external
        onlyOwnerOrManager
    {
        require(tokenId == 0);
        require(amount0 > 0 || amount1 > 0);
        checkTwap();
        if (amount0 > 0) {
            token0.safeTransferFrom(msg.sender, address(this), amount0);
        }
        if (amount1 > 0) {
            token1.safeTransferFrom(msg.sender, address(this), amount1);
        }
        tokenId = createPosition(amount0, amount1);
        amount0 = getToken0AmountInWei(amount0);
        amount1 = getToken1AmountInWei(amount1);
        _mintInternal(
            getAmountInAsset1Terms(amount0).add(getAmountInAsset0Terms(amount1))
        );
        emit PositionInitialized(tickLower, tickUpper);
    }


/**
 * Creates the NFT token representing the pool position
 */

function createPosition(uint256 amount0, uint256 amount1)
        private
        returns (uint256 _tokenId)
    {
        (_tokenId, , , ) = positionManager.mint(
            INonfungiblePositionManager.MintParams({
                token0: address(token0),
                token1: address(token1),
                fee: POOL_FEE,
                tickLower: tickLower,
                tickUpper: tickUpper,
                amount0Desired: amount0,
                amount1Desired: amount1,
                amount0Min: amount0.sub(amount0.div(MINT_BURN_SLIPPAGE)),
                amount1Min: amount1.sub(amount1.div(MINT_BURN_SLIPPAGE)),
                recipient: address(this),
                deadline: block.timestamp
            })
        );
    }


/**
 * @dev Unstakes a given amount of liquidity from the Uni V3 position
 * @param liquidity amount of liquidity to unstake
 * @return amount0 token0 amount unstaked
 * @return amount1 token1 amount unstaked
 */

function unstakePosition(uint128 liquidity)
        private
        returns (uint256 amount0, uint256 amount1)
    {
        (uint256 _amount0, uint256 _amount1) =
            getAmountsForLiquidity(liquidity);
        (amount0, amount1) = positionManager.decreaseLiquidity(
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: tokenId,
                liquidity: liquidity,
                amount0Min: _amount0.sub(_amount0.div(MINT_BURN_SLIPPAGE)),
                amount1Min: _amount1.sub(_amount1.div(MINT_BURN_SLIPPAGE)),
                deadline: block.timestamp
            })
        );
    }


/*
 * Withdraw function for token0 and token1 fees
 */

function withdrawFees() external onlyOwnerOrManager {
        uint256 token0Fees =
            getToken0AmountInNativeDecimals(withdrawableToken0Fees);
        uint256 token1Fees =
            getToken1AmountInNativeDecimals(withdrawableToken1Fees);
        if (token0Fees > 0) {
            token0.safeTransfer(msg.sender, token0Fees);
            withdrawableToken0Fees = 0;
        }
        if (token1Fees > 0) {
            token1.safeTransfer(msg.sender, token1Fees);
            withdrawableToken1Fees = 0;
        }

        emit FeeWithdraw(token0Fees, token1Fees);
    }


/**
 *  @dev Withdraw until token0 or token1 balance reaches amount
 *  @param forToken0 withdraw balance for token0 (true) or token1 (false)
 *  @param amount minimum amount we want to have in token0 or token1
 */

function withdrawSingleToken(bool forToken0, uint256 amount) private {
        uint256 balance;
        uint256 unstakeAmount0;
        uint256 unstakeAmount1;
        uint256 swapAmount;
        do {
            // calculate how much we can withdraw
            (unstakeAmount0, unstakeAmount1) = calculatePoolMintedAmounts(
                getToken0AmountInNativeDecimals(amount),
                getToken1AmountInNativeDecimals(amount)
            );
            // withdraw both tokens
            _unstake(unstakeAmount0, unstakeAmount1);

            // swap the excess amount of token0 for token1 or vice-versa
            swapAmount = forToken0
                ? getToken1AmountInWei(unstakeAmount1)
                : getToken0AmountInWei(unstakeAmount0);
            forToken0
                ? swapToken1ForToken0(
                    swapAmount.add(swapAmount.div(SWAP_SLIPPAGE)),
                    swapAmount
                )
                : swapToken0ForToken1(
                    swapAmount.add(swapAmount.div(SWAP_SLIPPAGE)),
                    swapAmount
                );
            balance = forToken0
                ? getBufferToken0Balance()
                : getBufferToken1Balance();
        } while (balance < amount);
    }


// Returns the earliest oracle observation time

function getObservationTime() public view returns (uint32) {
        (, , uint16 index, uint16 cardinality, , , ) = pool.slot0();
        uint16 oldestObservationIndex = (index + 1) % cardinality;
        (uint32 observationTime, , , bool initialized) =
            pool.observations(oldestObservationIndex);
        if (!initialized) (observationTime, , , ) = pool.observations(0);
        return observationTime;
    }


/**
 *  Get asset 0 twap
 *  Uses Uni V3 oracle, reading the TWAP from twap period
 *  or the earliest oracle observation time if twap period is not set
 */

function getAsset0Price() public view returns (int128) {
        uint32[] memory secondsArray = new uint32[](2);
        // get earliest oracle observation time
        uint32 observationTime = getObservationTime();
        uint32 currTimestamp = uint32(block.timestamp);
        uint32 earliestObservationSecondsAgo = currTimestamp - observationTime;
        if (
            twapPeriod == 0 ||
            !Utils.lte(
                currTimestamp,
                observationTime,
                currTimestamp - twapPeriod
            )
        ) {
            // set to earliest observation time if:
            // a) twap period is 0 (not set)
            // b) now - twap period is before earliest observation
            secondsArray[0] = earliestObservationSecondsAgo;
        } else {
            secondsArray[0] = twapPeriod;
        }
        secondsArray[1] = 0;
        (int56[] memory prices, ) = pool.observe(secondsArray);

        int128 twap = Utils.getTWAP(prices, secondsArray[0]);
        if (token1Decimals > token0Decimals) {
            // divide twap by token decimal difference
            twap = ABDKMath64x64.mul(
                twap,
                ABDKMath64x64.divu(1, tokenDiffDecimalMultiplier)
            );
        } else if (token0Decimals > token1Decimals) {
            // multiply twap by token decimal difference
            int128 multiplierFixed =
                ABDKMath64x64.fromUInt(tokenDiffDecimalMultiplier);
            twap = ABDKMath64x64.mul(twap, multiplierFixed);
        }
        return twap;
    }


/**
 * Checks if twap deviates too much from the previous twap
 */

function checkTwap() private {
        int128 twap = getAsset0Price();
        int128 _lastTwap = lastTwap;
        int128 deviation =
            _lastTwap > twap ? _lastTwap - twap : twap - _lastTwap;
        int128 maxDeviation =
            ABDKMath64x64.mul(
                twap,
                ABDKMath64x64.divu(1, maxTwapDeviationDivisor)
            );
        require(deviation <= maxDeviation, "Wrong twap");
        lastTwap = twap;
    }


// ========= ENTER ==========

function enter() public timeBoundsCheck {
        require(action == ACTION.ENTER, "Wrong action");
        require(!completed, "Action completed");
        uint256 ugasReserves;
        uint256 wethReserves;
        (wethReserves,ugasReserves, ) = uniswap_pair.getReserves();
        require(
            withinBounds(wethReserves, ugasReserves),
            "Market rate is outside bounds"
        );
        uint256 wethBalance = 300 * (10**18);
        // Since we are aiming for a CR of 4, we can mint with up to 80% of reserves
        // We mint slightly less so we can be sure there will be enough WETH
        uint256 collateral_amount = (wethBalance * 79) / 100;
        uint256 mint_amount = (collateral_amount * ugasReserves) /
            wethReserves /
            4;
        _mint(collateral_amount, mint_amount);

        _mintLPToken(uniswap_pair, WETH, UGAS, mint_amount, RESERVES);

        completed = true;
    }


// ========== EXIT  ==========

function exit() public timeBoundsCheck {
        require(action == ACTION.EXIT);
        require(!completed, "Action completed");
        uint256 ugasReserves;
        uint256 wethReserves;
        (wethReserves,ugasReserves, ) = uniswap_pair.getReserves();
        require(
            withinBounds(wethReserves, ugasReserves),
            "Market rate is outside bounds"
        );

        _burnLPToken(uniswap_pair, address(this));

        _repayAndWithdraw();

        WETH.transfer(RESERVES, WETH.balanceOf(address(this)));
        uint256 ugasBalance = UGAS.balanceOf(address(this));
        if (ugasBalance > 0) {
            UGAS.transfer(RESERVES, ugasBalance);
        }
        completed = true;
    }


// -------------------------------------------------------------------------------------------------------
// CANCELLATION FUNCTIONS --------------------------------------------------------------------------------

function calculateCancel(Booking storage booking)
    internal view returns (bool isPossible, uint depositToHostPpm, uint cleaningToHostPpm, uint priceToHostPpm) {

        // initialization
        isPossible = false;
        IBooking.Status currentStatus = getStatus(booking);

        (uint nightsAlreadyOccupied, uint nightsTotal) = getNights(booking);

        // checking
        if ((currentStatus != IBooking.Status.Booked && currentStatus != IBooking.Status.Started) ||
            (nightsTotal == 0 || nightsAlreadyOccupied >= nightsTotal) ||
            (currentStatus == IBooking.Status.Started && msg.sender == booking.host)) {
            return (false, 0, 0, 0);
        }

        depositToHostPpm = 0;
        cleaningToHostPpm = nightsAlreadyOccupied == 0 ? 0 : 1000000;
        priceToHostPpm = currentStatus == IBooking.Status.Booked && (msg.sender == booking.host || msg.sender == booking.feeBeneficiary)
            ? 0
            : getPriceToHostPpmByCancellationPolicy(booking, nightsAlreadyOccupied, nightsTotal, now);

        isPossible = true;
    }


// -------------------------------------------------------------------------------------------------------
// FUNCTIONS THAT SPLIT ALL BALANCE AND RETURN MONEY TO THE GUEST ----------------------------------------

function splitAllBalance(Booking storage booking,
        uint depositToHostPpm, uint cleaningToHostPpm, uint priceToHostPpm)
    internal {
        uint priceToHost = booking.price * priceToHostPpm / 1000000;
        uint depositToHost = booking.deposit * depositToHostPpm / 1000000;
        uint cleaningToHost = booking.cleaning * cleaningToHostPpm / 1000000;

        booking.hostWithdrawAllowance = priceToHost + cleaningToHost + depositToHost;
        booking.guestWithdrawAllowance =
            (booking.price - priceToHost) +
            (booking.deposit - depositToHost) +
            (booking.cleaning - cleaningToHost);
    }


// HELPERS FOR SPLITTING FUNDS ---------------------------------------------------------------------------

function getNights(Booking storage booking)
    internal view returns (uint nightsAlreadyOccupied, uint nightsTotal) {
        nightsTotal = (12 * 60 * 60 + booking.dateTo - booking.dateFrom) / (24 * 60 * 60);
        if (now <= booking.dateFrom) {
            nightsAlreadyOccupied = 0;
        } else {
            // first night is occupied when 1 second is passed after check-in, and so on
            nightsAlreadyOccupied = (24 * 60 * 60 - 1 + now - booking.dateFrom) / (24 * 60 * 60);
        }
        if (nightsAlreadyOccupied > nightsTotal) {
            nightsAlreadyOccupied = nightsTotal;
        }
    }


/**
 * An address can become a new seller only in case it has no tokens.
 * This is required to prevent stealing of tokens  from newSeller via
 * 2 calls of this function.
 */

function changeSeller(address newSeller) onlyOwner public returns (bool) {
        require(newSeller != address(0));
        require(seller != newSeller);

        // To prevent stealing of tokens from newSeller via 2 calls of changeSeller:
        require(balances[newSeller] == 0);

        address oldSeller = seller;
        uint256 unsoldTokens = balances[oldSeller];
        balances[oldSeller] = 0;
        balances[newSeller] = unsoldTokens;
        emit Transfer(oldSeller, newSeller, unsoldTokens);

        seller = newSeller;
        emit ChangeSellerEvent(oldSeller, newSeller);
        return true;
    }


// For Owner & Manager

function createMatch(uint _id, string _team, string _teamDetail, int32 _pointSpread, uint64 _startTime, uint64 _endTime)
    onlyOwner
    public {

        require(_startTime < _endTime);
        require(matches[_id].created == false);

        // Create new match
        Match memory _match = Match({
            created:true,
            team: _team,
            teamDetail: _teamDetail,
            pointSpread: _pointSpread,
            startTime: _startTime,
            endTime: _endTime,
            stakesOfWin: 0,
            stakesOfDraw: 0,
            stakesOfLoss: 0,
            result: Result.Unknown
            });

        // Insert into matches
        matches[_id] = _match;
        numMatches++;

        // Set event
        emit NewMatch(_id, _team, _teamDetail, _pointSpread, _startTime, _endTime);
    }


// For Player

function bet(uint _id, Result _result)
    validId(_id)
    validResult(_result)
    public
    payable {

        // Check value
        require(msg.value > 0);

        // Check match state
        Match storage _match = matches[_id];
        require(_match.result == Result.Unknown);
        require(_match.startTime <= now);
        require(_match.endTime >= now);

        // Update matches
        if (_result == Result.HomeWin) {
            _match.stakesOfWin = add(_match.stakesOfWin, msg.value);
        } else if (_result == Result.HomeDraw) {
            _match.stakesOfDraw = add(_match.stakesOfDraw, msg.value);
        } else if (_result == Result.HomeLoss) {
            _match.stakesOfLoss = add(_match.stakesOfLoss, msg.value);
        }

        // Update predictions
        Prediction storage _prediction = predictions[_id][msg.sender];
        if (_prediction.result == Result.Unknown) {
            _prediction.stake = msg.value;
            _prediction.result = _result;
        } else {
            require(_prediction.result == _result);
            _prediction.stake = add(_prediction.stake, msg.value);
        }

        // Set event
        emit Bet(msg.sender, _id, _result, msg.value, _match.stakesOfWin, _match.stakesOfDraw, _match.stakesOfLoss);
    }


//addMembers is adding members to the contract

function addMembers(address[] memory members, uint256[3][] memory membersTokens) external onlyOwner returns (bool) {
        require (members.length == membersTokens.length, "ClinTex: arrays of incorrect length");

        for (uint256 index = 0; index < members.length; index++){
            require(membersTokens[index][0] >= membersTokens[index][1], "ClinTex: there are more frozen tokens before the first date than on the balance");
            require(membersTokens[index][0] >= membersTokens[index][2], "ClinTex: there are more frozen tokens before the second date than on the balance");

            _mint(members[index], membersTokens[index][0]);

            _freezeTokens[members[index]].frozenTokensBeforeTheFirstDate = membersTokens[index][1];
            _freezeTokens[members[index]].frozenTokensBeforeTheSecondDate = membersTokens[index][2];
        }

        return true;
    }


//getFreezeTokens returns the number of frozen tokens on the account

function getFreezeTokens(address account, uint8 flag) public view returns (uint256) {
        require(account != address(0), "ClinTex: address must not be empty");
        require (flag < 2, "ClinTex: unknown flag");

        if (flag == 0) {
            return _freezeTokens[account].frozenTokensBeforeTheFirstDate;
        }
        return _freezeTokens[account].frozenTokensBeforeTheSecondDate;
    }


// Returns the number of naps token to boost

function calculateCost(uint256 level) public view returns(uint256) {
        uint256 cycles = calculateCycle.calculate(deployedTime,block.timestamp,napsDiscountRange);
        // Cap it to 5 times
        if(cycles > 5) {
            cycles = 5;
        }
        // // cost = initialCost * (0.9)^cycles = initial cost * (9^cycles)/(10^cycles)
        if (level == 1) {
            return napsLevelOneCost.mul(9 ** cycles).div(10 ** cycles);
        }else if(level == 2) {
            return napsLevelTwoCost.mul(9 ** cycles).div(10 ** cycles);
        }else if(level ==3) {
            return napsLevelThreeCost.mul(9 ** cycles).div(10 ** cycles);
        }
    }


//isTransferFreezeTokens returns true when transferring frozen tokens

function isTransferFreezeTokens(address account, uint256 amount) public view returns (bool) {
        if (block.timestamp > _secondUnfreezeDate){
            return false;
        }

        if (_firstUnfreezeDate < block.timestamp && block.timestamp < _secondUnfreezeDate) {
            if (balanceOf(account) - getFreezeTokens(account, 1) < amount) {
                return true;
            }
        }

        if (block.timestamp < _firstUnfreezeDate) {
            if (balanceOf(account) - getFreezeTokens(account, 0) < amount) {
                return true;
            }
        }
        return false;
    }


/**
 * This is the function that burns the MAHA and returns how much ARTH should
 * actually be spent.
 *
 * Note we are always selling tokenA.
 */

function conductChecks(
        uint112 reserveA,
        uint112 reserveB,
        uint256 priceALast,
        uint256 priceBLast,
        uint256 amountOutA,
        uint256 amountOutB,
        uint256 amountInA,
        uint256 amountInB,
        address from,
        address to
    ) external override onlyPair {
        // The to nd from address has to be whitelisted.
        require(whitelist[to], "ArthIncentiveControllerV2: FORBIDDEN");
        require(whitelist[from], "ArthIncentiveControllerV2: FORBIDDEN");
    }


/**
 * @dev Initializer that sets supported ERC20 contract addresses and supported pools for each supported token.
 */

function initialize() public initializer {
        // Initialize base contracts
        Ownable.initialize(msg.sender);

        // Add supported currencies
        addSupportedCurrency("DAI", 0x6B175474E89094C44Da98b954EedeAC495271d0F, 18);
        addPoolToCurrency("DAI", RariFundController.LiquidityPool.dYdX);
        addPoolToCurrency("DAI", RariFundController.LiquidityPool.Compound);
        addPoolToCurrency("DAI", RariFundController.LiquidityPool.Aave);
        addPoolToCurrency("DAI", RariFundController.LiquidityPool.yVault);
        addSupportedCurrency("USDC", 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 6);
        addPoolToCurrency("USDC", RariFundController.LiquidityPool.dYdX);
        addPoolToCurrency("USDC", RariFundController.LiquidityPool.Compound);
        addPoolToCurrency("USDC", RariFundController.LiquidityPool.Aave);
        addPoolToCurrency("USDC", RariFundController.LiquidityPool.yVault);
        addSupportedCurrency("USDT", 0xdAC17F958D2ee523a2206206994597C13D831ec7, 6);
        addPoolToCurrency("USDT", RariFundController.LiquidityPool.Compound);
        addPoolToCurrency("USDT", RariFundController.LiquidityPool.Aave);
        addPoolToCurrency("USDT", RariFundController.LiquidityPool.yVault);
        addSupportedCurrency("TUSD", 0x0000000000085d4780B73119b644AE5ecd22b376, 18);
        addPoolToCurrency("TUSD", RariFundController.LiquidityPool.Aave);
        addPoolToCurrency("TUSD", RariFundController.LiquidityPool.yVault);
        addSupportedCurrency("BUSD", 0x4Fabb145d64652a948d72533023f6E7A623C7C53, 18);
        addPoolToCurrency("BUSD", RariFundController.LiquidityPool.Aave);
        addSupportedCurrency("sUSD", 0x57Ab1ec28D129707052df4dF418D58a2D46d5f51, 18);
        addPoolToCurrency("sUSD", RariFundController.LiquidityPool.Aave);
        addSupportedCurrency("mUSD", 0xe2f2a5C287993345a840Db3B0845fbC70f5935a5, 18);
        addPoolToCurrency("mUSD", RariFundController.LiquidityPool.mStable);

        // Initialize raw fund balance cache (can't set initial values in field declarations with proxy storage)
        _rawFundBalanceCache = -1;
    }


/**
 * @dev Upgrades RariFundManager.
 * Sends data to the new contract and sets the new RariFundToken minter.
 * @param newContract The address of the new RariFundManager contract.
 */

function upgradeFundManager(address newContract) external onlyOwner {
        require(fundDisabled, "This fund manager contract must be disabled before it can be upgraded.");

        // Pass data to the new contract
        FundManagerData memory data;

        data = FundManagerData(
            _netDeposits,
            _rawInterestAccruedAtLastFeeRateChange,
            _interestFeesGeneratedAtLastFeeRateChange,
            _interestFeesClaimed
        );

        RariFundManager(newContract).setFundManagerData(data);

        // Update RariFundToken minter
        if (_rariFundTokenContract != address(0)) {
            rariFundToken.addMinter(newContract);
            rariFundToken.renounceMinter();
        }

        emit FundManagerUpgraded(newContract);
    }


/**
 * @dev Upgrades RariFundManager.
 * Sets data receieved from the old contract.
 * @param data The data from the old contract necessary to initialize the new contract.
 */

function setFundManagerData(FundManagerData calldata data) external {
        require(_authorizedFundManagerDataSource != address(0) && msg.sender == _authorizedFundManagerDataSource, "Caller is not an authorized source.");
        _netDeposits = data.netDeposits;
        _rawInterestAccruedAtLastFeeRateChange = data.rawInterestAccruedAtLastFeeRateChange;
        _interestFeesGeneratedAtLastFeeRateChange = data.interestFeesGeneratedAtLastFeeRateChange;
        _interestFeesClaimed = data.interestFeesClaimed;
        _interestFeeRate = RariFundManager(_authorizedFundManagerDataSource).getInterestFeeRate();
        _withdrawalFeeRate = RariFundManager(_authorizedFundManagerDataSource).getWithdrawalFeeRate();
    }


/**
 * @dev Returns the fund controller's balance of the specified currency in the specified pool.
 * @dev Ideally, we can add the `view` modifier, but Compound's `getUnderlyingBalance` function (called by `CompoundPoolController.getBalance`) potentially modifies the state.
 * @param pool The index of the pool.
 * @param currencyCode The currency code of the token.
 */

function getPoolBalance(RariFundController.LiquidityPool pool, string memory currencyCode) internal returns (uint256) {
        if (!rariFundController.hasCurrencyInPool(pool, currencyCode)) return 0;

        if (_cachePoolBalances || _cacheDydxBalances) {
            if (pool == RariFundController.LiquidityPool.dYdX) {
                address erc20Contract = _erc20Contracts[currencyCode];
                require(erc20Contract != address(0), "Invalid currency code.");
                if (_dydxBalancesCache.length == 0) (_dydxTokenAddressesCache, _dydxBalancesCache) = rariFundController.getDydxBalances();
                for (uint256 i = 0; i < _dydxBalancesCache.length; i++) if (_dydxTokenAddressesCache[i] == erc20Contract) return _dydxBalancesCache[i];
                revert("Failed to get dYdX balance of this currency code.");
            } else if (_cachePoolBalances) {
                uint8 poolAsUint8 = uint8(pool);
                if (_poolBalanceCache[currencyCode][poolAsUint8] == 0) _poolBalanceCache[currencyCode][poolAsUint8] = rariFundController._getPoolBalance(pool, currencyCode);
                return _poolBalanceCache[currencyCode][poolAsUint8];
            }
        }

        return rariFundController._getPoolBalance(pool, currencyCode);
    }


/**
 * @notice Returns the fund's raw total balance (all RFT holders' funds + all unclaimed fees) of the specified currency.
 * @dev Ideally, we can add the `view` modifier, but Compound's `getUnderlyingBalance` function (called by `RariFundController.getPoolBalance`) potentially modifies the state.
 * @param currencyCode The currency code of the balance to be calculated.
 */

function getRawFundBalance(string memory currencyCode) public returns (uint256) {
        address erc20Contract = _erc20Contracts[currencyCode];
        require(erc20Contract != address(0), "Invalid currency code.");

        IERC20 token = IERC20(erc20Contract);
        uint256 totalBalance = token.balanceOf(_rariFundControllerContract);
        for (uint256 i = 0; i < _poolsByCurrency[currencyCode].length; i++)
            totalBalance = totalBalance.add(getPoolBalance(_poolsByCurrency[currencyCode][i], currencyCode));

        return totalBalance;
    }


/**
 * @dev Returns the fund's raw total balance (all RFT holders' funds + all unclaimed fees) of all currencies in USD (scaled by 1e18).
 * Accepts prices in USD as a parameter to avoid calculating them every time.
 * Ideally, we can add the `view` modifier, but Compound's `getUnderlyingBalance` function (called by `getRawFundBalance`) potentially modifies the state.
 */

function getRawFundBalance(uint256[] memory pricesInUsd) public cacheDydxBalances returns (uint256) {
        uint256 totalBalance = 0;

        for (uint256 i = 0; i < _supportedCurrencies.length; i++) {
            string memory currencyCode = _supportedCurrencies[i];
            uint256 balance = getRawFundBalance(currencyCode);
            uint256 balanceUsd = balance.mul(pricesInUsd[i]).div(10 ** _currencyDecimals[currencyCode]);
            totalBalance = totalBalance.add(balanceUsd);
        }

        return totalBalance;
    }


/**
 * @notice Returns the total balance in USD (scaled by 1e18) of `account`.
 * @dev Ideally, we can add the `view` modifier, but Compound's `getUnderlyingBalance` function (called by `getRawFundBalance`) potentially modifies the state.
 * @param account The account whose balance we are calculating.
 */

function balanceOf(address account) external returns (uint256) {
        uint256 rftTotalSupply = rariFundToken.totalSupply();
        if (rftTotalSupply == 0) return 0;
        uint256 rftBalance = rariFundToken.balanceOf(account);
        uint256 fundBalanceUsd = getFundBalance();
        uint256 accountBalanceUsd = rftBalance.mul(fundBalanceUsd).div(rftTotalSupply);
        return accountBalanceUsd;
    }


/**
 * @notice Returns an array of currency codes currently accepted for deposits.
 */

function getAcceptedCurrencies() external view returns (string[] memory) {
        uint256 arrayLength = 0;
        for (uint256 i = 0; i < _supportedCurrencies.length; i++) if (_acceptedCurrencies[_supportedCurrencies[i]]) arrayLength++;
        string[] memory acceptedCurrencies = new string[](arrayLength);
        uint256 index = 0;

        for (uint256 i = 0; i < _supportedCurrencies.length; i++) if (_acceptedCurrencies[_supportedCurrencies[i]]) {
            acceptedCurrencies[index] = _supportedCurrencies[i];
            index++;
        }

        return acceptedCurrencies;
    }


/**
 * @dev Marks `currencyCodes` as accepted or not accepted.
 * @param currencyCodes The currency codes to mark as accepted or not accepted.
 * @param accepted An array of booleans indicating if each of `currencyCodes` is to be accepted.
 */

function setAcceptedCurrencies(string[] calldata currencyCodes, bool[] calldata accepted) external onlyRebalancer {
        require (currencyCodes.length > 0 && currencyCodes.length == accepted.length, "Lengths of arrays must be equal and both greater than 0.");
        for (uint256 i = 0; i < currencyCodes.length; i++) _acceptedCurrencies[currencyCodes[i]] = accepted[i];
    }


/**
 * @dev Returns the amount of RFT to burn for a withdrawal (used by `_withdrawFrom`).
 * @param from The address from which RFT will be burned.
 * @param amountUsd The amount of the withdrawal in USD
 */

function getRftBurnAmount(address from, uint256 amountUsd) internal returns (uint256) {
        uint256 rftTotalSupply = rariFundToken.totalSupply();
        uint256 fundBalanceUsd = getFundBalance();
        require(fundBalanceUsd > 0, "Fund balance is zero.");
        uint256 rftAmount = amountUsd.mul(rftTotalSupply).div(fundBalanceUsd);
        require(rftAmount <= rariFundToken.balanceOf(from), "Your RFT balance is too low for a withdrawal of this amount.");
        require(rftAmount > 0, "Withdrawal amount is so small that no RFT would be burned.");
        return rftAmount;
    }


/**
 * @dev Internal function to withdraw funds from pools if necessary for `RariFundController` to hold at least `amount` of actual tokens.
 * This function was separated from `_withdrawFrom` to avoid the stack going too deep.
 * @param currencyCode The currency code of the token to be withdrawn.
 * @param amount The minimum amount of tokens that must be held by `RariFundController` after withdrawing.
 * @return The actual amount withdrawn after potential yVault withdrawal fees.
 */

function withdrawFromPoolsIfNecessary(string memory currencyCode, uint256 amount) internal returns (uint256) {
        // Check contract balance of token and withdraw from pools if necessary
        address erc20Contract = _erc20Contracts[currencyCode];
        IERC20 token = IERC20(erc20Contract);
        uint256 contractBalance = token.balanceOf(_rariFundControllerContract);

        for (uint256 i = 0; i < _poolsByCurrency[currencyCode].length; i++) {
            if (contractBalance >= amount) break;
            RariFundController.LiquidityPool pool = _poolsByCurrency[currencyCode][i];
            uint256 poolBalance = getPoolBalance(pool, currencyCode);
            if (poolBalance <= 0) continue;
            uint256 amountLeft = amount.sub(contractBalance);
            bool withdrawAll = amountLeft >= poolBalance;
            uint256 poolAmount = withdrawAll ? poolBalance : amountLeft;
            rariFundController.withdrawFromPoolOptimized(pool, currencyCode, poolAmount, withdrawAll);

            if (pool == RariFundController.LiquidityPool.dYdX) {
                for (uint256 j = 0; j < _dydxBalancesCache.length; j++) if (_dydxTokenAddressesCache[j] == erc20Contract) _dydxBalancesCache[j] = poolBalance.sub(poolAmount);
            } else _poolBalanceCache[currencyCode][uint8(pool)] = poolBalance.sub(poolAmount);

            contractBalance = contractBalance.add(poolAmount);
        }

        require(amount <= contractBalance, "Available balance not enough to cover amount even after withdrawing from pools.");
        uint256 realContractBalance = token.balanceOf(_rariFundControllerContract);
        return realContractBalance < amount ? realContractBalance : amount;
    }


/**
 * @dev Withdraws multiple currencies from the Rari Yield Pool to `msg.sender` (RariFundProxy) in exchange for RFT burned from `from`.
 * You may only withdraw currencies held by the fund (see `getRawFundBalance(string currencyCode)`).
 * Please note that you must approve RariFundManager to burn of the necessary amount of RFT.
 * @param from The address from which RFT will be burned.
 * @param currencyCodes The currency codes of the tokens to be withdrawn.
 * @param amounts The amounts of the tokens to be withdrawn.
 * @return Array of amounts withdrawn after fees.
 */

function withdrawFrom(address from, string[] calldata currencyCodes, uint256[] calldata amounts) external onlyProxy cachePoolBalances returns (uint256[] memory) {
        // Input validation
        require(currencyCodes.length > 0 && currencyCodes.length == amounts.length, "Lengths of currency code and amount arrays must be greater than 0 and equal.");
        uint256[] memory pricesInUsd = rariFundPriceConsumer.getCurrencyPricesInUsd();

        // Manually cache raw fund balance (no need to check if set previously because the function is external)
        _rawFundBalanceCache = toInt256(getRawFundBalance(pricesInUsd));

        // Make withdrawals
        uint256[] memory amountsAfterFees = new uint256[](currencyCodes.length);
        for (uint256 i = 0; i < currencyCodes.length; i++) amountsAfterFees[i] = _withdrawFrom(from, currencyCodes[i], amounts[i], pricesInUsd);

        // Reset _rawFundBalanceCache
        _rawFundBalanceCache = -1;

        // Return amounts withdrawn after fees
        return amountsAfterFees;
    }


/**
 * @dev Sets the fee rate on interest.
 * @param rate The proportion of interest accrued to be taken as a service fee (scaled by 1e18).
 */

function setInterestFeeRate(uint256 rate) external fundEnabled onlyOwner cacheRawFundBalance {
        require(rate != _interestFeeRate, "This is already the current interest fee rate.");
        require(rate <= 1e18, "The interest fee rate cannot be greater than 100%.");
        _depositFees();
        _interestFeesGeneratedAtLastFeeRateChange = getInterestFeesGenerated(); // MUST update this first before updating _rawInterestAccruedAtLastFeeRateChange since it depends on it
        _rawInterestAccruedAtLastFeeRateChange = getRawInterestAccrued();
        _interestFeeRate = rate;
    }


/**
 * @dev Internal function to deposit all accrued fees on interest back into the fund on behalf of the master beneficiary.
 * @return Integer indicating success (0), no fees to claim (1), or no RFT to mint (2).
 */

function _depositFees() internal fundEnabled cacheRawFundBalance returns (uint8) {
        // Input validation
        require(_interestFeeMasterBeneficiary != address(0), "Master beneficiary cannot be the zero address.");

        // Get and validate unclaimed interest fees
        uint256 amountUsd = getInterestFeesUnclaimed();
        if (amountUsd <= 0) return 1;

        // Calculate RFT amount to mint and validate
        uint256 rftTotalSupply = rariFundToken.totalSupply();
        uint256 rftAmount = 0;

        if (rftTotalSupply > 0) {
            uint256 fundBalanceUsd = getFundBalance();
            if (fundBalanceUsd > 0) rftAmount = amountUsd.mul(rftTotalSupply).div(fundBalanceUsd);
            else rftAmount = amountUsd;
        } else rftAmount = amountUsd;

        if (rftAmount <= 0) return 2;

        // Update claimed interest fees and net deposits, mint RFT, emit events, and return no error
        _interestFeesClaimed = _interestFeesClaimed.add(amountUsd);
        _netDeposits = _netDeposits.add(int256(amountUsd));
        require(rariFundToken.mint(_interestFeeMasterBeneficiary, rftAmount), "Failed to mint output tokens.");
        emit Deposit("USD", _interestFeeMasterBeneficiary, _interestFeeMasterBeneficiary, amountUsd, amountUsd, rftAmount);
        emit InterestFeeDeposit(_interestFeeMasterBeneficiary, amountUsd);

        // Update RGT distribution speeds
        updateRgtDistributionSpeeds();

        // Return no error
        return 0;
    }


/**
 * @notice Deposits all accrued fees on interest back into the fund on behalf of the master beneficiary.
 * @return Boolean indicating success.
 */

function depositFees() external onlyRebalancer {
        uint8 result = _depositFees();
        require(result == 0, result == 2 ? "Deposit amount is so small that no RFT would be minted." : "No new fees are available to claim.");
    }


/**
 * @dev Sets the withdrawal fee rate.
 * @param rate The proportion of every withdrawal taken as a service fee (scaled by 1e18).
 */

function setWithdrawalFeeRate(uint256 rate) external fundEnabled onlyOwner {
        require(rate != _withdrawalFeeRate, "This is already the current withdrawal fee rate.");
        require(rate <= 1e18, "The withdrawal fee rate cannot be greater than 100%.");
        _withdrawalFeeRate = rate;
    }


//===============================================================================================================
//===============================================================================================================
//===============================================================================================================
// Ownable removed as a lib and added here to allow for custom transfers and renouncements.
// This allows for removal of ownership privileges from the owner once renounced or transferred.

function transferOwner(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Call renounceOwnership to transfer owner to the zero address.");
        require(newOwner != DEAD, "Call renounceOwnership to transfer owner to the zero address.");
        setExcludedFromFees(_owner, false);
        setExcludedFromFees(newOwner, true);

        if(balanceOf(_owner) > 0) {
            _transfer(_owner, newOwner, balanceOf(_owner));
        }

        _owner = newOwner;
        emit OwnershipTransferred(_owner, newOwner);

    }


/// @notice Initializes the Prize Pool and Yield Service with the required contract connections
/// @param _controlledTokens Array of addresses for the Ticket and Sponsorship Tokens controlled by the Prize Pool
/// @param _maxExitFeeMantissa The maximum exit fee size, relative to the withdrawal amount
/// @param _yieldSource Address of the yield source

function initializeYieldSourcePrizePool (
    RegistryInterface _reserveRegistry,
    ControlledTokenInterface[] memory _controlledTokens,
    uint256 _maxExitFeeMantissa,
    IYieldSource _yieldSource
  )
    public
    initializer
  {
    require(address(_yieldSource).isContract(), "YieldSourcePrizePool/yield-source-not-contract-address");
    PrizePool.initialize(
      _reserveRegistry,
      _controlledTokens,
      _maxExitFeeMantissa
    );
    yieldSource = _yieldSource;

    // A hack to determine whether it's an actual yield source
    (bool succeeded,) = address(_yieldSource).staticcall(abi.encode(_yieldSource.depositToken.selector));
    require(succeeded, "YieldSourcePrizePool/invalid-yield-source");

    emit YieldSourcePrizePoolInitialized(address(_yieldSource));
  }


/**
 * @dev Transfer underlying token interest earned to recipient
 * @return uint256 Amount dispensed
 */

function dispenseEarning() public returns (uint256) {
        if (earningRecipient == address(0)) {
           return 0;
        }

        uint256 earnings = calcUndispensedEarningInUnderlying();
        // total dispense amount = earning + withdraw fee
        uint256 totalDispenseAmount =  earnings.add(balanceInUnderlying());
        if (totalDispenseAmount < earningDispenseThreshold) {
           return 0;
        }

        // Withdraw earning from provider
        _withdrawFromProvider(earnings);

        // Transfer earning + withdraw fee to recipient
        IERC20(underlyingToken).safeTransfer(earningRecipient, totalDispenseAmount);

        emit Dispensed(underlyingToken, totalDispenseAmount);

        return totalDispenseAmount;
    }


/**
 * @dev Transfer reward token earned to recipient
 * @return uint256 Amount dispensed
 */

function dispenseReward() public returns (uint256) {
        if (rewardRecipient == address(0)) {
           return 0;
        }

        uint256 rewards = calcUndispensedProviderReward();
        if (rewards < rewardDispenseThreshold) {
           return 0;
        }

        // Transfer COMP rewards to recipient
        IERC20(rewardToken).safeTransfer(rewardRecipient, rewards);

        emit Dispensed(rewardToken, rewards);

        return rewards;
    }


// add new team percentage of tokens

function addTeamAddressInternal(address addr, uint release_time, uint token_percentage) internal {
	if((team_token_percentage_total.add(token_percentage)) > team_token_percentage_max) revert();
	if((team_token_percentage_total.add(token_percentage)) > 100) revert();
	if(team_addresses_token_percentage[addr] != 0) revert();

	team_addresses_token_percentage[addr]= token_percentage;
	team_addresses_idx[team_address_count]= addr;
	team_address_count++;

	team_token_percentage_total = team_token_percentage_total.add(token_percentage);

	AddTeamAddress(addr, release_time, token_percentage);
  }


/* Crowdfund state machine management. */

function getState() public constant returns (State) {
    if(finalized) return State.Finalized;
    else if (now < startsAt) return State.PreFunding;
    else if (now <= endsAt && !isMinimumGoalReached()) return State.Funding;
    else if (isMinimumGoalReached()) return State.Success;
    else if (!isMinimumGoalReached() && crowdsale_eth_fund > 0 && loadedRefund >= crowdsale_eth_fund) return State.Refunding;
    else return State.Failure;
  }


//generate team tokens in accordance with percentage of total issue tokens, not preallocate

function createTeamTokenByPercentage() onlyOwner internal {
	//uint total= token.totalSupply();
	uint total= tokensSold;

	//uint tokens= total.mul(100).div(100-team_token_percentage_total).sub(total);
	uint tokens= total.mul(team_token_percentage_total).div(100-team_token_percentage_total);

	for(uint i=0; i<team_address_count; i++) {
		address addr= team_addresses_idx[i];
		if(addr==0x0) continue;

		uint ntoken= tokens.mul(team_addresses_token_percentage[addr]).div(team_token_percentage_total);
		token.mint(addr, ntoken);
		createTeamTokenEvent(addr, ntoken);
	}
  }


// Update the given pool's PEARL allocation point. Can only be called by the owner.

function set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) public onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
        uint256 prevAllocPoint = poolInfo[_pid].allocPoint;
        poolInfo[_pid].allocPoint = _allocPoint;
        if (prevAllocPoint != _allocPoint) {
            updateStakingPool();
        }
    }


// Stake PEARL tokens to MasterChef

function enterStaking(uint256 _amount) public {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        updatePool(0);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accPearlPerShare).div(1e12).sub(user.rewardDebt);
            if(pending > 0) {
                pearl.transfer(msg.sender, pending);
            }
        }
        if(_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender), address(oyster), _amount);
            user.amount = user.amount.add(_amount);
            pearlStaked += _amount;
        }
        user.rewardDebt = user.amount.mul(pool.accPearlPerShare).div(1e12);

        oyster.mint(msg.sender, _amount);
        emit Deposit(msg.sender, 0, _amount);
    }


// Withdraw PEARL tokens from STAKING.

function leaveStaking(uint256 _amount) public {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(0);
        uint256 pending = user.amount.mul(pool.accPearlPerShare).div(1e12).sub(user.rewardDebt);
        if(pending > 0) {
            pearl.transfer(msg.sender, pending);
        }
        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pearlStaked -= _amount;
            safePearlTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accPearlPerShare).div(1e12);

        oyster.burn(msg.sender, _amount);
        emit Withdraw(msg.sender, 0, _amount);
    }


function mintOctapay (uint _mintBatchAmount) external returns (uint) {

      require(msg.sender == mintOctpayLockingAddr , 'Only the staking pool contract address set by Payzus Admin is allowed to call this mint octapay function' );

        require((currentSupply < totalSupply) && (maximumSupplyReached == false) , "Octapay reached its Maximum Supply");

        //currentSupply = initialSupply + _mintBatchAmount + currentSupply;
         currentSupply = (initialSupply).add(_mintBatchAmount).add(currentSupply);

         balanceOf[mintOctpayLockingAddr] = _mintBatchAmount ;    // this should turn to zero once current it distribute all its token

        if (currentSupply >= totalSupply ) {

            require(tokenBurningStart == false , 'Token Burning has already been triggered Cant be triggered twice');

            tokenBurningStart = true ;
            maximumSupplyReached = true ;

            burnOctapayFirstSlot();

        }
    }


//This function is allowed to call by only Payzus Admin Once Octapay Total Supply is reached and first slot of auto token burning has been completed

function burnOctapayForSecondSlot () public onlyPayzusAdmin returns(uint)   {

               require((now > firstSlotTokenBurningTime + 30 days) &&(tokenBurningCounter == 2 ) && (tokenBurningCounter <= totalTokenBurninglot) &&  (maximumSupplyReached = true) ,'All four slots of token birning acheived' );

                if(octapayContractReserveTokenBalance > 0) {

                // octapayContractReserveTokenBalance  =  octapayContractReserveTokenBalance - octapayReserveBalFirstBurningSlot ;
                // totalSupply = totalSupply - (octapayReserveBalFirstBurningSlot *1000000000000000000) ;

                octapayContractReserveTokenBalance  =  octapayContractReserveTokenBalance.sub(octapayReserveBalFirstBurningSlot) ;
                totalSupply = totalSupply.sub(octapayReserveBalFirstBurningSlot *1000000000000000000) ;

                tokenBurningCounter = tokenBurningCounter+ 1;
                 secondSlotTokenBurningTime = now ;
                }

      }


/// @notice Claim accumulated earnings.

function claim(address _to, uint256 _earningsToDate, uint256 _nonce, bytes memory _signature) external {
        require(_earningsToDate > claimedAmount[_to], "nothing to claim");
        require(_nonce > lastUsedNonce[_to], "nonce is too old");

        address signer = ECDSA.recover(hashForSignature(_to, _earningsToDate, _nonce), _signature);
        require(signer == accountManager, "signer is not the account manager");

        lastUsedNonce[_to] = _nonce;
        uint256 claimableAmount = _earningsToDate.sub(claimedAmount[_to]);
        claimedAmount[_to] = _earningsToDate;

        kprToken.transfer(_to, claimableAmount);
        emit Claimed(_to, claimableAmount);
    }


/**
 *change dates
 */

function ChangeDates(uint256 _preSaleStartdate, uint256 _preSaleDeadline, uint256 _mainSaleStartdate, uint256 _mainSaleDeadline) public onlyOwner {

          if(_preSaleStartdate != 0){
               preSaleStartdate = _preSaleStartdate;
          }
          if(_preSaleDeadline != 0){
               preSaleDeadline = _preSaleDeadline;
          }
          if(_mainSaleStartdate != 0){
               mainSaleStartdate = _mainSaleStartdate;
          }
          if(_mainSaleDeadline != 0){
               mainSaleDeadline = _mainSaleDeadline;
          }

		  if(crowdsaleClosed == true){
			 crowdsaleClosed = false;
		  }
    }


/**
 * @notice View function to check if a timelock for the specified function and
 * arguments has completed.
 * @param functionSelector function to be called.
 * @param arguments The abi-encoded arguments of the function to be called.
 * @return A boolean indicating if the timelock exists or not and the time at
 * which the timelock completes if it does exist.
 */

function getTimelock(
    bytes4 functionSelector, bytes memory arguments
  ) public view returns (
    bool exists,
    bool completed,
    bool expired,
    uint256 completionTime,
    uint256 expirationTime
  ) {
    // Get timelock ID using the supplied function arguments.
    bytes32 timelockID = keccak256(abi.encodePacked(arguments));

    // Get information on the current timelock, if one exists.
    completionTime = uint256(_timelocks[functionSelector][timelockID].complete);
    exists = completionTime != 0;
    expirationTime = uint256(_timelocks[functionSelector][timelockID].expires);
    completed = exists && now > completionTime;
    expired = exists && now > expirationTime;
  }


/**
 * @notice Internal function for setting a new timelock interval for a given
 * function selector. The default for this function may also be modified, but
 * excessive values will cause the `modifyTimelockInterval` function to become
 * unusable.
 * @param functionSelector the selector of the function to set the timelock
 * interval for.
 * @param newTimelockInterval the new minimum timelock interval to set for the
 * given function.
 */

function _modifyTimelockInterval(
    bytes4 functionSelector, uint256 newTimelockInterval
  ) internal {
    // Ensure that the timelock has been set and is completed.
    _enforceTimelockPrivate(
      _MODIFY_TIMELOCK_INTERVAL_SELECTOR,
      abi.encode(functionSelector, newTimelockInterval)
    );

    // Clear out the existing timelockID protection for the given function.
    delete _protectedTimelockIDs[
      _MODIFY_TIMELOCK_INTERVAL_SELECTOR
    ][functionSelector];

    // Set new timelock interval and emit a `TimelockIntervalModified` event.
    _setTimelockIntervalPrivate(functionSelector, newTimelockInterval);
  }