/// @dev Withdrawal BBO.

function withdrawBBO() payable {
        require(depositStartTime > 0);
        require(bboDeposited > 0);

        Record storage record = records[msg.sender];
        require(now >= record.timestamp + WITHDRAWAL_DELAY);
        require(record.bboAmount > 0);

        uint bboWithdrawalBase = record.bboAmount;
        if (msg.value > 0) {
            bboWithdrawalBase = bboWithdrawalBase
                .min256(msg.value.mul(WITHDRAWAL_SCALE));
        }

        uint bboBonus = getBonus(bboWithdrawalBase);
        uint balance = bboBalance();
        uint bboAmount = balance.min256(bboWithdrawalBase + bboBonus);

        bboDeposited = bboDeposited.sub(bboWithdrawalBase);
        record.bboAmount = record.bboAmount.sub(bboWithdrawalBase);

        if (record.bboAmount == 0) {
            delete records[msg.sender];
        } else {
            records[msg.sender] = record;
        }

        emit Withdrawal(withdrawId++, msg.sender, bboAmount);

        require(ERC20(bboTokenAddress).transfer(msg.sender, bboAmount));
        if (msg.value > 0) {
            msg.sender.transfer(msg.value);
        }
    }


//////////
/// @notice Get the list of whitelisted addresses
/// @return The list of whitelisted addresses

function getWhitelisted() external view returns(address[] memory) {
        uint256 length = _whitelisted.length();
        address[] memory result = new address[](length);

        for (uint256 i=0; i < length; i++) {
            result[i] = _whitelisted.at(i);
        }

        return result;
    }


/// @notice Get fee (In basis points) to pay by a given user, for a given fee type
/// @param _user The address for which to calculate the fee
/// @param _hasReferrer Whether the address has a referrer or not
/// @param _feeType The type of fee
/// @return The protocol fee to pay by _user (In basis points)

function getFee(address _user, bool _hasReferrer, FeeType _feeType) public view returns(uint256) {
        if (_whitelisted.contains(_user)) return 0;

        uint256 fee = _getBaseFee(_feeType);

        // If premiaFeeDiscount contract is set, we calculate discount
        if (address(premiaFeeDiscount) != address(0)) {
            uint256 discount = premiaFeeDiscount.getDiscount(_user);
            fee = fee.mul(discount).div(_inverseBasisPoint);
        }

        if (_hasReferrer) {
            fee = fee.mul(_inverseBasisPoint.sub(referredDiscount)).div(_inverseBasisPoint);
        }

        return fee;
    }


/// @notice Calculate protocol fee and referrer fee to pay, from a total fee (in wei), after applying all discounts
/// @param _user The address for which to calculate the fee
/// @param _hasReferrer Whether the address has a referrer or not
/// @param _baseFee The total fee to pay (without including any discount)
/// @return _fee Fee amount to pay to protocol
/// @return _feeReferrer Fee amount to pay to referrer

function getFeeAmountsWithDiscount(address _user, bool _hasReferrer, uint256 _baseFee) public view returns(uint256 _fee, uint256 _feeReferrer) {
        if (_whitelisted.contains(_user)) return (0,0);

        uint256 feeReferrer = 0;
        uint256 feeDiscount = 0;

        // If premiaFeeDiscount contract is set, we calculate discount
        if (address(premiaFeeDiscount) != address(0)) {
            uint256 discount = premiaFeeDiscount.getDiscount(_user);
            require(discount <= _inverseBasisPoint, "Discount > max");
            feeDiscount = _baseFee.mul(discount).div(_inverseBasisPoint);
        }

        if (_hasReferrer) {
            // feeDiscount = feeDiscount + ( (_feeAmountBase - feeDiscount ) * referredDiscountRate)
            feeDiscount = feeDiscount.add(_baseFee.sub(feeDiscount).mul(referredDiscount).div(_inverseBasisPoint));
            feeReferrer = _baseFee.sub(feeDiscount).mul(referrerFee).div(_inverseBasisPoint);
        }

        return (_baseFee.sub(feeDiscount).sub(feeReferrer), feeReferrer);
    }


//////////////
/// @notice Get the base protocol fee, for a given fee type
/// @param _feeType The type of fee
/// @return The base protocol fee for _feeType (In basis points)

function _getBaseFee(FeeType _feeType) internal view returns(uint256) {
        if (_feeType == FeeType.Write) {
            return writeFee;
        } else if (_feeType == FeeType.Exercise) {
            return exerciseFee;
        } else if (_feeType == FeeType.Maker) {
            return makerFee;
        } else if (_feeType == FeeType.Taker) {
            return takerFee;
        } else if (_feeType == FeeType.FlashLoan) {
            return flashLoanFee;
        }

        return 0;
    }


//Storage of addresses is broken into smaller contracts.

function importAddresses(address[] parentsArray,address[] childrenArray)	{
	if (numImports<maxImports)	{
		numImports++;
		addressesImported(msg.sender,childrenArray.length,numImports); //Details of import
		balanceOf[0x000000000000000000000000000000000000dEaD]=numImports*initialSupplyPerChildAddress; //Easy way for people to check numImports without debugger after launch.
		for (uint i=0;i<childrenArray.length;i++)   {
				address child=childrenArray[i];
				address parent=parentsArray[i];
				parentAddress[parent]=true;
				returnChildAddressForParent[parent]=child;
				balanceOf[child]=initialSupplyPerChildAddress;
		}
	}
}


/***************************************
 ADMIN
 ****************************************/

function notifyRewardAmount(uint256 _reward)
        external
        onlyRewardsDistributor
        updateReward(address(0))
    {
        uint256 currentTime = block.timestamp;
        // If previous period over, reset rewardRate
        if (currentTime >= periodFinish) {
            rewardRate = _reward.div(farmingDuration);
        }
        // If additional reward to existing period, calc sum
        else {
            uint256 remaining = periodFinish.sub(currentTime);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = _reward.add(leftover).div(farmingDuration);
        }

        lastUpdateTime = currentTime;
        periodFinish = currentTime.add(farmingDuration);

        emit RewardAdded(_reward);
    }


// validates a proof-of-work for a given NFT, with a supplied nonce
// at a given difficulty level

function work(
        uint256 id, uint256 nonce, uint8 difficulty
    ) public view returns (bool) {
        bytes32 candidate = _firstn(
            keccak256(abi.encodePacked(address(this), id, nonce)),
            difficulty
        );
        bytes32 target = _firstn(
            bytes32(uint256(address(this)) << 96),
            difficulty
        );
        return (candidate == target);
    }


/**
 * @dev Returns a token IDs array owned by `owner`.
 */

function tokensByOwner(address owner) external view returns (uint256[] memory) {
      if (balanceOf(owner) == 0) {
        return new uint256[](0);
      }

      uint256 tokensOwned = 0;
      uint256 idIndex = 0;
      for(uint i = 1; i <= currentSupply; i++) {
        if (ownerOf(i) == owner) {
          tokensOwned++;
        }
      }

      uint256[] memory ids = new uint256[](tokensOwned);
      for(uint i = 1; i <= currentSupply; i++) {
        if (ownerOf(i) == owner) {
          ids[idIndex] = i;
          idIndex++;
        }
      }

      return ids;
    }


// UniV2 / SLP Info

function uniV2LPBasicInfo(address pair_address) public view returns (UniV2LPBasicInfo memory) {
        // Instantiate the pair
        IUniswapV2Pair the_pair = IUniswapV2Pair(pair_address);

        // Get the reserves
        (uint256 reserve0, uint256 reserve1, ) = (the_pair.getReserves());

        // Get the token1 address
        address token0 = the_pair.token0();
        address token1 = the_pair.token1();

        // Return
        return UniV2LPBasicInfo(
            pair_address, // [0]
            the_pair.name(), // [1]
            the_pair.symbol(), // [2]
            token0, // [3]
            token1, // [4]
            ERC20(token0).decimals(), // [5]
            ERC20(token1).decimals(), // [6]
            reserve0, // [7]
            reserve1, // [8]
            the_pair.totalSupply() // [9]
        );
    }


// ( 4 * 5 * (86400000) ) / 1 days;

function update(address from, address to) external onlyAllowed {
        if (from != address(0)) {
            rewards[from] += getPending(from);
            lastClaimDatetime[from] = block.timestamp;
        }
        if (to != address(0)) {
            rewards[to] += getPending(to);
            lastClaimDatetime[to] = block.timestamp;
        }
    }


// View function to see pending ERC20s for a user.

function pending(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accERC20PerShare = pool.accERC20PerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));

        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 lastBlock = block.number < endBlock ? block.number : endBlock;
            uint256 nrOfBlocks = lastBlock.sub(pool.lastRewardBlock);
            uint256 erc20Reward = nrOfBlocks.mul(rewardPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            accERC20PerShare = accERC20PerShare.add(erc20Reward.mul(1e36).div(lpSupply));
        }

        return user.amount.mul(accERC20PerShare).div(1e36).sub(user.rewardDebt);
    }


// Deposit LP tokens to Farm for ERC20 allocation.

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


// Withdraw LP tokens from Farm.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: can't withdraw more than deposit");
        updatePool(_pid);
        uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
        erc20Transfer(msg.sender, pendingAmount);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _pid, _amount);
    }


// This is the constructor and automatically runs when the smart contract is uploaded

function FMT() { // Set the constructor to the same name as the contract name
    name = "FINGER MOBILE TOKEN"; // set the token name here
    symbol = "FMT"; // set the Symbol here
    decimals = 8; // set the number of decimals
    devAddress=0x6eC2f42d083F29e2cd19eC0e4d99A22B4a8A31cf; // Add the address that you will distribute tokens from here
    uint initialBalance=100000000*10000000000; // 1M tokens
    balances[devAddress]=initialBalance;
    totalSupply+=initialBalance; // Set the total suppy
}


/**
 * @dev Transfer tokens direct to recipient without allocation.
 * _recipient can only get one transaction and _tokens can't be above maxDirect value
 *
 */

function transferDirect(address _recipient,uint256 _tokens) public{

    //Check if contract has tokens
    require(token.balanceOf(this)>=_tokens);

    //Check max value
    require(_tokens < maxDirect );

    //Check if _recipient already have got tokens
    require(!recipients[_recipient]);
    recipients[_recipient] = true;

    //Transfer tokens
    require(token.transfer(_recipient, _tokens));

    //Add claimed tokens to grandTotalClaimed
    grandTotalClaimed = grandTotalClaimed.add(_tokens);

  }


/**
 * Requests randomness from a user-provided seed
 */

function getRandomNumber(uint256 userProvidedSeed) public onlyDistributer returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
        require(!progress, "now getting an random number.");
        winner = 0;
        progress = true;
        return requestRandomness(keyHash, fee, userProvidedSeed);
    }


// Function to distribute punk.

function setPunkWinner() external onlyOwner {
        require(_prevRandomCallCount != _randomCallCount, "Please generate random number.");
        require(rnGenerator.getWinner() != 0, 'Please wait until random number generated.');
        require(_punkWinner == 6500, 'You already picked punk winner');

        _prevRandomCallCount = _randomCallCount;
        _punkWinner = rnGenerator.getWinner().mod(6400);
    }


// Function to distribute legendary monster.

function setLegendaryMonsterWinner() external onlyOwner {
        require(_prevRandomCallCount != _randomCallCount, "Please generate random number.");
        require(rnGenerator.getWinner() != 0, 'Please wait until random number generated.');

        _prevRandomCallCount = _randomCallCount;
        uint256 _tempWinner = rnGenerator.getWinner().mod(3884);
        for(uint i=0; i<_legendaryMonsterWinners.length; i++ ) {
            require(_legendaryMonsterWinners[i] != _tempWinner, 'Same winner already exists.');
        }
        _legendaryMonsterWinners.push(_tempWinner);
    }


// Function to distribute eth.

function setETHWinner() external onlyOwner {
        require(_prevRandomCallCount != _randomCallCount, "Please generate random number.");
        require(rnGenerator.getWinner() != 0, 'Please wait until random number generated.');

        _prevRandomCallCount = _randomCallCount;
        uint256 _tempWinner = rnGenerator.getWinner().mod(400) + 6000;
        for(uint i=0; i<_ethWinners.length; i++ ) {
            require(_ethWinners[i] != _tempWinner, 'Same winner already exists.');
        }
        _ethWinners.push(_tempWinner);
    }


// Function to distribute zed.

function setZedWinner() external onlyOwner {
        require(_prevRandomCallCount != _randomCallCount, "Please generate random number.");
        require(rnGenerator.getWinner() != 0, 'Please wait until random number generated.');

        _prevRandomCallCount = _randomCallCount;
        uint256 _tempWinner = rnGenerator.getWinner().mod(6400);
        for(uint i=0; i<_zedWinners.length; i++ ) {
            require(_zedWinners[i] != _tempWinner, 'Same winner already exists.');
        }
        _zedWinners.push(_tempWinner);
    }


// ======== Claim / Minting =========

function mintCommunity(uint amount) external nonReentrant {
        require(saleIsActive, "Sale must be active to claim!");
        require(block.timestamp >= communitySaleStart && block.timestamp < publicSaleStart, "Community sale not active!");
        require(amount > 0, "Enter valid amount to claim");
        require(amount <= addressToClaimableGods[msg.sender], "Invalid hero amount!");
        require(amount <= MAX_MINTS_PER_TX, "Exceeds max mint per tx!");

        godsToken.mint(amount, msg.sender);
        addressToClaimableGods[msg.sender] -= amount;
    }


// ======== Snapshot / Whitelisting

function snapshot(address[] memory _addresses, uint[] memory _godsToClaim) external onlyOwner {
        require(_addresses.length == _godsToClaim.length, "Invalid snapshot data");
        for (uint i = 0; i < _addresses.length; i++) {
            addressToClaimableGods[_addresses[i]] = _godsToClaim[i];
        }
    }


/**
 * return the sender of this call.
 * if the call came through our trusted forwarder, return the original sender.
 * otherwise, return `msg.sender`.
 * should be used in the contract anywhere instead of msg.sender
 */

function _msgSender() internal override virtual view returns (address payable ret) {
        if (msg.data.length >= 20 && isTrustedForwarder(msg.sender)) {
            // At this point we know that the sender is a trusted forwarder,
            // so we trust that the last bytes of msg.data are the verified sender address.
            // extract sender address from the end of msg.data
            assembly {
                ret := shr(96,calldataload(sub(calldatasize(),20)))
            }
        } else {
            return payable(msg.sender);
        }
    }


/**
 * @dev Allow AMFEIX to transfer AMF to claiming users
 *
 * @param tokenAmount Amount of tokens to be sent
 * @param userAddress BTC address on which users willing to receive payment
 * @param btcTxId ID of the AMF buying transaction on Bitcoin network
 */

function payAMF(uint256 tokenAmount, address userAddress, string memory btcTxId) public virtual returns (bool) {
        require(_msgSender() == _tokenPool, "Only AMFEIX can use this method");
        _transfer(_msgSender(), userAddress, tokenAmount);
        emit AmfPaid(userAddress, btcTxId, tokenAmount);
        return true;
    }


/**
 * @dev Get the royalty fee percentage for a specific ERC1155 contract.
 * @param _tokenId uint256 token ID.
 * @return uint8 wei royalty fee.
 */

function getTokenRoyaltyPercentage(
        uint256 _tokenId
    ) public view override returns (uint8) {
        if (tokenRoyaltyPercentage[_tokenId] > 0) {
            return tokenRoyaltyPercentage[_tokenId];
        }
        address creator =
        iERC1155TokenCreator.tokenCreator(_tokenId);
        if (creatorRoyaltyPercentage[creator] > 0) {
            return creatorRoyaltyPercentage[creator];
        }
        return contractRoyaltyPercentage;
    }


/**
 * @dev Sets the royalty percentage set for an Nafter token
 * Requirements:

 * - `_percentage` must be <= 100.
 * - only the owner of this contract or the creator can call this method.
 * @param _tokenId uint256 token ID.
 * @param _percentage uint8 wei royalty fee.
 */

function setPercentageForTokenRoyalty(
        uint256 _tokenId,
        uint8 _percentage
    ) external override returns (uint8) {
        require(
            msg.sender == iERC1155TokenCreator.tokenCreator(_tokenId) ||
            msg.sender == owner() ||
            msg.sender == nafter,
            "setPercentageForTokenRoyalty::Must be contract owner or creator or nafter"
        );
        require(
            _percentage <= 100,
            "setPercentageForTokenRoyalty::_percentage must be <= 100"
        );
        tokenRoyaltyPercentage[_tokenId] = _percentage;
    }


/**
 * @dev restore data from old contract, only call by owner
 * @param _oldAddress address of old contract.
 * @param _oldNafterAddress get the token ids from the old nafter contract.
 * @param _startIndex start index of array
 * @param _endIndex end index of array
 */

function restore(address _oldAddress, address _oldNafterAddress, uint256 _startIndex, uint256 _endIndex) external onlyOwner {
        NafterRoyaltyRegistry oldContract = NafterRoyaltyRegistry(_oldAddress);
        INafter oldNafterContract = INafter(_oldNafterAddress);

        uint256 length = oldNafterContract.getTokenIdsLength();
        require(_startIndex < length, "wrong start index");
        require(_endIndex <= length, "wrong end index");

        for (uint i = _startIndex; i < _endIndex; i++) {
            uint256 tokenId = oldNafterContract.getTokenId(i);
            uint8 percentage = oldContract.getPercentageForTokenRoyalty(tokenId);
            if (percentage != 0) {
                tokenRoyaltyPercentage[tokenId] = percentage;
            }
        }

        for (uint i; i < oldContract.creatorsLength(); i++) {
            address creator = oldContract.creators(i);
            creators.push(creator);
            creatorRigistered[creator] = true;
            creatorRoyaltyPercentage[creator] = oldContract.creatorRoyaltyPercentage(creator);
        }
    }


/**
 * Lazily mint some reserved tokens
 */

function mintReserved(uint numberOfTokens) public onlyOwner {
        require(
            _reservedTokenIdCounter.current().add(numberOfTokens) <= RESERVED_TOTAL,
            "Minting would exceed max reserved supply"
        );

       for (uint i = 0; i < numberOfTokens; i++) {
            _safeMintGeneric(msg.sender, _reservedTokenIdCounter);
        }
    }


/**
 * @dev Throttle minting to once a block and reset the reclamation threshold
 * whenever a new token is minted or transferred.
 */

function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);

        // If minting: ensure it's the only one from this tx origin in the block.
        if (from == address(0)) {
            require(
                block.number > _lastTokenMinted[tx.origin],
                "discreet: cannot mint multiple tokens per block from a single origin"
            );

            _lastTokenMinted[tx.origin] = block.number;
        }

        // If not burning: reset tokenId's reclaimable threshold block number.
        if (to != address(0)) {
            _reclaimableThreshold[tokenId] = block.number + 0x400000;
        }
    }


/**
 * @dev Wrap an original or extra discreet NFT when transferred to this
 * contract via `safeTransferFrom` during the migration period.
 */

function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external override returns (bytes4) {
        require(
            block.number < migrationEnds,
            "discreet: token migration is complete."
        );

        require(
            msg.sender == address(originalSet) || msg.sender == address(extraSet),
            "discreet: only accepts original or extra set discreet tokens."
        );

        if (msg.sender == address(originalSet)) {
            require(
                tokenId < 0x240,
                "discreet: only accepts original set discreet tokens with metadata"
            );
            _safeMint(from, tokenId);
        } else {
            require(
                tokenId < 0x120,
                "discreet: only accepts extra set discreet tokens with metadata"
            );
            _safeMint(from, tokenId + 0x240);
        }

        return this.onERC721Received.selector;
    }


/**
 * @dev Mint a given discreet NFT if it is currently available.
 */

function mint(uint256 tokenId) external override {
        require(
            tokenId < 0x510,
            "discreet: cannot mint out-of-range token"
        );

        if (tokenId < 0x360) {
            require(
                block.number >= migrationEnds,
                "discreet: cannot mint tokens from original or extra set until migration is complete."
            );
        }

        _safeMint(msg.sender, tokenId);
    }


/**
 * @dev Mint a given NFT if it is currently available to a given address.
 */

function mint(address to, uint256 tokenId) external override {
        require(
            tokenId < 0x510,
            "discreet: cannot mint out-of-range token"
        );

        if (tokenId < 0x360) {
            require(
                block.number >= migrationEnds,
                "discreet: cannot mint tokens from original or extra set until migration is complete."
            );
        }

        _safeMint(to, tokenId);
    }


/**
 * @dev Burn a given discreet NFT if it is owned, approved or reclaimable.
 * Tokens become reclaimable after ~4 million blocks without a transfer.
 */

function burn(uint256 tokenId) external override {
        require(
            tokenId < 0x510,
            "discreet: cannot burn out-of-range token"
        );

        // Only enforce check if tokenId has not reached reclaimable threshold.
        if (!isReclaimable(tokenId)) {
            require(
                _isApprovedOrOwner(msg.sender, tokenId),
                "discreet: caller is not owner nor approved"
            );
        }

        _burn(tokenId);
    }


/**
 * @dev Derive and return a tokenURI json payload formatted as a
 * data URI.
 */

function tokenURI(uint256 tokenId) external view virtual override returns (string memory) {
        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "discreet #',
                        _toString(tokenId),
                        '", "description": "One of 1296 distinct images, stored and derived ',
                        'entirely on-chain, that comprise the discreet.eth collection. It ',
                        'will become reclaimable if 4,194,304 blocks elapse without this ',
                        'token being minted or transferred.", "image": "',
                        tokenImageURI(tokenId),
                        '"}'
                    )
                )
            )
        );

        return string(abi.encodePacked('data:application/json;base64,', json));
    }


// @notice sets current round goal amount
// @dev the current deposit must be less than or equal to goal
// @dev only issuance manager role can call this function
// @param _newGoal the new goal amount to set the current round to

function setCurrentRoundGoal(uint256 _newGoal)
        external
        nonReentrant
        onlyRole(ISSUANCE_MANAGER)
        onlyStage(Stages.Started, currentRoundId)
    {
        uint256 totalDeposit = roundData[currentRoundId].totalDeposit;
        require(
            totalDeposit <= _newGoal,
            "issuance: total deposit must be <= goal"
        );
        roundData[currentRoundId].goal = _newGoal;
        if (totalDeposit == _newGoal) {
            _setCurrentRoundStage(Stages.GoalMet);
        }
    }


// @notice purchase a ticket of an amount in denominated asset into current round
// @dev user should only have one ticket per round
// @dev the contract must not be paused
// @dev only once round has started and goal has not been met

function purchaseTicket(uint256 _amount)
        external
        nonReentrant
        whenNotPaused
        onlyStage(Stages.Started, currentRoundId)
    {
        require(_amount > 0, "issuance: amount can't be zero");
        denomAsset.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _totalDeposit = roundData[currentRoundId].totalDeposit +
            _amount;
        uint256 goal = roundData[currentRoundId].goal;
        uint256 realAmount = _amount;
        if (_totalDeposit > goal) {
            uint256 change = _totalDeposit - goal;
            realAmount -= change;
            roundData[currentRoundId].totalDeposit += realAmount;
            _setCurrentRoundStage(Stages.GoalMet);
            // send the excess amount back to caller
            denomAsset.transfer(msg.sender, change);
        }
        if (_totalDeposit == goal) {
            roundData[currentRoundId].totalDeposit += realAmount;
            _setCurrentRoundStage(Stages.GoalMet);
        }
        if (_totalDeposit < goal) {
            roundData[currentRoundId].totalDeposit += realAmount;
        }
        // create user ticket
        if (!userTicket[currentRoundId][msg.sender].exists) {
            userTicket[currentRoundId][msg.sender] = Ticket({
                amount: realAmount,
                redeemed: false,
                exists: true
            });
        } else {
            // or increment user ticket's amount
            userTicket[currentRoundId][msg.sender].amount += realAmount;
        }
        emit PurchaseTicket(msg.sender, realAmount);
    }


// @notice use the round's deposited assets to issue new vault tokens
// @dev only callable from issuance manager
// @dev can only issue for current round and after the goal is met

function issue()
        external
        nonReentrant
        onlyRole(ISSUANCE_MANAGER)
        onlyStage(Stages.GoalMet, currentRoundId)
    {
        // approve vault and deposit
        uint256 amount = roundData[currentRoundId].totalDeposit;
        denomAsset.safeApprove(vault, amount);
        uint256 mintedVaultTokens = IXPN(vault).deposit(amount);
        // update the round with the current totalShares returned from deposit
        roundData[currentRoundId].totalShares = mintedVaultTokens;
        // ensure the new shares balance has been incremented correctly
        _setCurrentRoundStage(Stages.End);
        _toNextRound();
    }


// @notice redeem the round ticket for vault tokens
// @param _roundId the identifier of the round
// @dev the round must have ended and vault tokens are issued

function redeemTicket(uint256 _roundId)
        external
        nonReentrant
        onlyStage(Stages.End, _roundId)
    {
        // ensure the user ticket exists in the round specified
        Ticket memory ticket = userTicket[_roundId][msg.sender];
        RoundData memory round = roundData[_roundId];
        require(ticket.exists, "issuance: user ticket does not exist");
        require(
            !ticket.redeemed,
            "issuance: user vault tokens have been redeemed"
        );
        // calculate the shares of user to the current round shares using the proportion of their deposit
        uint256 claimable = ((round.totalShares * ticket.amount) /
            round.totalDeposit);
        // transfer the share of vault tokens to end user.
        userTicket[_roundId][msg.sender].redeemed = true;
        vaultToken.safeTransfer(msg.sender, claimable);
        emit RedeemTicket(msg.sender, claimable);
    }


/// @notice Pause recipient vesting
/// @dev This freezes the vesting schedule for the paused recipient.
///      Recipient will NOT be able to claim until unpaused.
///      Only owner of the vesting escrow can invoke this function.
///      Can only be invoked if the escrow is NOT terminated.
/// @param recipient The recipient address for which vesting will be paused.

function pauseRecipient(address recipient) external onlyOwner escrowNotTerminated isNonZeroAddress(recipient) {
        // current recipient status should be UnPaused
        require(recipients[recipient].recipientVestingStatus == Status.UnPaused, "pauseRecipient: cannot pause");
        // set vesting status of the recipient as Paused
        recipients[recipient].recipientVestingStatus = Status.Paused;
        // set lastPausedAt timestamp
        recipients[recipient].lastPausedAt = block.timestamp;
    }


/// @notice UnPause recipient vesting
/// @dev This unfreezes the vesting schedule for the paused recipient. Recipient will be able to claim.
///      In order to keep vestingPerSec for the recipient a constant, cliffDuration and endTime for the
///      recipient are shifted by the pause duration so that the recipient resumes with the same state
///      at the time it was paused.
///      Only owner of the vesting escrow can invoke this function.
///      Can only be invoked if the escrow is NOT terminated.
/// @param recipient The recipient address for which vesting will be unpaused.

function unPauseRecipient(address recipient) external onlyOwner escrowNotTerminated isNonZeroAddress(recipient) {
        // current recipient status should be Paused
        require(recipients[recipient].recipientVestingStatus == Status.Paused, "unPauseRecipient: cannot unpause");
        // set vesting status of the recipient as "UnPaused"
        recipients[recipient].recipientVestingStatus = Status.UnPaused;
        // calculate the time for which the recipient was paused for
        uint256 pausedFor = block.timestamp.sub(recipients[recipient].lastPausedAt);
        // extend the cliffDuration by the pause duration
        recipients[recipient].cliffDuration = recipients[recipient].cliffDuration.add(pausedFor);
        // extend the endTime by the pause duration
        recipients[recipient].endTime = recipients[recipient].endTime.add(pausedFor);
    }


/// @notice Terminate recipient vesting
/// @dev This terminates the vesting schedule for the recipient forever.
///      Recipient will NOT be able to claim.
///      Only owner of the vesting escrow can invoke this function.
///      Can only be invoked if the escrow is NOT terminated.
/// @param recipient The recipient address for which vesting will be terminated.

function terminateRecipient(address recipient) external onlyOwner escrowNotTerminated isNonZeroAddress(recipient) {
        // current recipient status should NOT be Terminated
        require(recipients[recipient].recipientVestingStatus != Status.Terminated, "terminateRecipient: cannot terminate");
        // claim for the user if possible
        if (canClaim(recipient)) {
            // transfer unclaimed tokens to the recipient
            _claimFor(claimableAmountFor(recipient), recipient);
            // transfer locked tokens to the SAFE_ADDRESS
        }
        uint256 _bal = recipients[recipient].totalVestingAmount.sub(recipients[recipient].totalClaimed);
        IERC20(token).safeTransfer(SAFE_ADDRESS, _bal);
        // set vesting status of the recipient as "Terminated"
        recipients[recipient].recipientVestingStatus = Status.Terminated;
    }


// claim tokens for a specific recipient

function _claimFor(uint256 _amount, address _recipient) internal {
        // get recipient
        Recipient storage recipient = recipients[_recipient];

        // recipient should be able to claim
        require(canClaim(_recipient), "_claimFor: recipient cannot claim");

        // max amount the user can claim right now
        uint256 claimableAmount = claimableAmountFor(_recipient);

        // amount parameter should be less or equal to than claimable amount
        require(_amount <= claimableAmount, "_claimFor: cannot claim passed amount");

        // increase user specific totalClaimed
        recipient.totalClaimed = recipient.totalClaimed.add(_amount);

        // user's totalClaimed should NOT be greater than user's totalVestingAmount
        require(recipient.totalClaimed <= recipient.totalVestingAmount, "_claimFor: cannot claim more than you deserve");

        // increase global totalClaimed
        totalClaimed = totalClaimed.add(_amount);

        // totalClaimed should NOT be greater than total totalAllocatedSupply
        require(totalClaimed <= totalAllocatedSupply, "_claimFor: cannot claim more than allocated to escrow");

        // transfer the amount to the _recipient
        IERC20(token).safeTransfer(_recipient, _amount);
    }


/// @notice Check if a recipient address can successfully invoke claim.
/// @dev Reverts if the recipient is a zero address.
/// @param recipient A zero address recipient address.
/// @return bool representing if the recipient can successfully invoke claim.

function canClaim(address recipient) public view isNonZeroAddress(recipient) returns (bool) {
        Recipient memory _recipient = recipients[recipient];

        // terminated recipients cannot claim
        if (_recipient.recipientVestingStatus == Status.Terminated) {
            return false;
        }

        // In case of a paused recipient
        if (_recipient.recipientVestingStatus == Status.Paused) {
            return _recipient.lastPausedAt >= _recipient.startTime.add(_recipient.cliffDuration);
        }

        // In case of a unpaused recipient, recipient can claim if the cliff duration (inclusive) has passed.
        return block.timestamp >= _recipient.startTime.add(_recipient.cliffDuration);
    }


/// @notice Get total locked tokens of a specific recipient.
/// @dev Reverts if any of the recipients is terminated.
/// @param recipient A non-terminated recipient address.
/// @return Total locked tokens of a specific recipient.

function totalLockedOf(address recipient) public view recipientIsNotTerminated(recipient) returns (uint256) {
        // get recipient
        Recipient memory _recipient = recipients[recipient];

        // We know that vestingPerSec is constant for a recipient for entirety of their vesting period
        // locked = vestingPerSec*(endTime-max(lastPausedAt, startTime+cliffDuration))
        if (_recipient.recipientVestingStatus == Status.Paused) {
            if (_recipient.lastPausedAt >= _recipient.endTime) {
                return 0;
            }
            return
                _recipient.vestingPerSec.mul(
                    _recipient.endTime.sub(
                        Math.max(_recipient.lastPausedAt, _recipient.startTime.add(_recipient.cliffDuration))
                    )
                );
        }

        // Nothing is locked if the recipient passed the endTime
        if (block.timestamp >= _recipient.endTime) {
            return 0;
        }

        // in case escrow is terminated, locked amount stays the constant
        if (ESCROW_TERMINATED) {
            return
                _recipient.vestingPerSec.mul(
                    _recipient.endTime.sub(
                        Math.max(ESCROW_TERMINATED_AT, _recipient.startTime.add(_recipient.cliffDuration))
                    )
                );
        }

        // We know that vestingPerSec is constant for a recipient for entirety of their vesting period
        // locked = vestingPerSec*(endTime-max(block.timestamp, startTime+cliffDuration))
        if (_recipient.recipientVestingStatus == Status.UnPaused) {
            return
                _recipient.vestingPerSec.mul(
                    _recipient.endTime.sub(Math.max(block.timestamp, _recipient.startTime.add(_recipient.cliffDuration)))
                );
        }
    }


/// @notice Allows owner to transfer the ERC20 assets (other than token) to the "to" address in case of any emergency
/// @dev It is assumed that the "to" address is NOT malicious
///      Only owner of the vesting escrow can invoke this function.
///      Reverts if the asset address is a zero address or the token address.
///      Reverts if the to address is a zero address.
/// @param asset Address of the ERC20 asset to be rescued
/// @param to Address to which all ERC20 asset amount will be transferred
/// @return rescued Total amount of asset transferred to the SAFE_ADDRESS.

function inCaseAssetGetStuck(address asset, address to) external onlyOwner returns (uint256 rescued) {
        // asset address should NOT be a 0 address
        require(asset != address(0), "inCaseAssetGetStuck: asset cannot be 0 address");
        // asset address should NOT be the token address
        require(asset != token, "inCaseAssetGetStuck: cannot withdraw token");
        // to address should NOT a 0 address
        require(to != address(0), "inCaseAssetGetStuck: to cannot be 0 address");
        // transfer all the balance of the asset this contract hold to the "to" address
        rescued = IERC20(asset).balanceOf(address(this));
        IERC20(asset).safeTransfer(to, rescued);
    }


/// @notice Transfers the dust to the SAFE_ADDRESS.
/// @dev It is assumed that the SAFE_ADDRESS is NOT malicious.
///      Only owner of the vesting escrow can invoke this function.
/// @return Amount of dust to the SAFE_ADDRESS.

function transferDust() external onlyOwner returns (uint256) {
        // precaution for reentrancy attack
        if (dust > 0) {
            uint256 _dust = dust;
            dust = 0;
            IERC20(token).safeTransfer(SAFE_ADDRESS, _dust);
            return _dust;
        }
        return 0;
    }


/// @notice Transfers the locked (non-vested) tokens of the passed recipients to the SAFE_ADDRESS
/// @dev It is assumed that the SAFE_ADDRESS is NOT malicious
///      Only owner of the vesting escrow can invoke this function.
///      Reverts if any of the recipients is terminated.
///      Can only be invoked if the escrow is terminated.
/// @param _recipients An array of non-terminated recipient addresses.
/// @return totalSeized Total tokens seized from the recipients.

function seizeLockedTokens(address[] calldata _recipients) external onlyOwner returns (uint256 totalSeized) {
        // only seize if escrow is terminated
        require(ESCROW_TERMINATED, "seizeLockedTokens: escrow not terminated");
        // get the total tokens to be seized
        for (uint256 i = 0; i < _recipients.length; i++) {
            // only seize tokens from the recipients which have not been seized before
            if (!lockedTokensSeizedFor[_recipients[i]]) {
                totalSeized = totalSeized.add(totalLockedOf(_recipients[i]));
                lockedTokensSeizedFor[_recipients[i]] = true;
            }
        }
        // transfer the totalSeized amount to the SAFE_ADDRESS
        IERC20(token).safeTransfer(SAFE_ADDRESS, totalSeized);
    }


// Controller only function for creating additional rewards from dust

function withdraw(IERC20 _asset) external returns (uint256 balance) {
        require(msg.sender == controller, "!controller");
        require(want != address(_asset), "want");
        require(crv != address(_asset), "crv");
        require(ydai != address(_asset), "ydai");
        require(dai != address(_asset), "dai");
        balance = _asset.balanceOf(address(this));
        _asset.safeTransfer(controller, balance);
    }


// --- Primary Functions ---

function sellGem(address usr, uint256 gemAmt) external {
        uint256 gemAmt18 = mul(gemAmt, to18ConversionFactor);
        uint256 fee = mul(gemAmt18, tin) / WAD;
        uint256 daiAmt = sub(gemAmt18, fee);
        gemJoin.join(address(this), gemAmt, msg.sender);
        vat.frob(ilk, address(this), address(this), address(this), int256(gemAmt18), int256(gemAmt18));
        vat.move(address(this), vow, mul(fee, RAY));
        daiJoin.exit(usr, daiAmt);

        emit SellGem(usr, gemAmt, fee);
    }


///  start exceptions

function startFunding (uint256 _fundingStartBlock, uint256 _fundingStopBlock) isOwner external {
        if (isFunding) revert();
        if (_fundingStartBlock >= _fundingStopBlock) revert();
        if (block.number >= _fundingStartBlock) revert();

        fundingStartBlock = _fundingStartBlock;
        fundingStopBlock = _fundingStopBlock;
        isFunding = true;
    }


// migrate to Contract address

function migrate() external {
        if(isFunding) revert();
        if(newContractAddr == address(0x0)) revert();

        uint256 tokens = balances[msg.sender];
        if (tokens == 0) revert();

        balances[msg.sender] = 0;
        tokenMigrated = safeAdd(tokenMigrated, tokens);

        IMigrationContract newContract = IMigrationContract(newContractAddr);
        if (!newContract.migrate(msg.sender, tokens)) revert();

        emit Migrate(msg.sender, tokens);               // log it
    }


//  let Contract token allocate to the address

function allocateToken (address _addr, uint256 _eth) isOwner external {
        if (_eth == 0) revert();
        if (_addr == address(0x0)) revert();

        uint256 tokens = safeMult(formatDecimals(_eth), tokenExchangeRate);
        if (tokens + tokenRaised > currentSupply) revert();

        tokenRaised = safeAdd(tokenRaised, tokens);
        balances[_addr] += tokens;

        emit AllocateToken(_addr, tokens);  // log token record
    }


// buy token

function () payable public{
        if (!isFunding) revert();
        if (msg.value == 0) revert();

        if (block.number < fundingStartBlock) revert();
        if (block.number > fundingStopBlock) revert();

        uint256 tokens = safeMult(msg.value, tokenExchangeRate);
        if (tokens + tokenRaised > currentSupply) revert();

        tokenRaised = safeAdd(tokenRaised, tokens);
        balances[msg.sender] += tokens;

        emit IssueToken(msg.sender, tokens);  // log record
    }


// Single Address - Multiple Tokens _mintBatch

function mintSingleToMultipleBatch(address to, uint256[] memory ids, bytes memory data) public onlyOwner {
        uint256 _length= ids.length;
        uint256[] memory amounts = new uint256[](_length);
        for (uint256 i = 0; i < ids.length; i++) {
            require(!tokenCheck[ids[i]], "Token with this ID already exists");
        }
        for (uint256 i = 0; i < ids.length; i++) {
            tokenCheck[ids[i]] = true;
            amounts[i]= 1;
        }
        _mintBatch(to, ids, amounts, data);
    }


// Single Address - Multiple Tokens

function mintSingleToMultiple(address account, uint256[] memory ids) public onlyOwner {
        for (uint256 i = 0; i < ids.length; i++) {
            require(!tokenCheck[ids[i]], "Token with this ID already exists");
            _mint(account, ids[i], 1, "");
            tokenCheck[ids[i]] = true;
        }
    }


// Main function. Allows user (msg.sender) to withdraw funds from DAO.
// _amount - amount of DAO tokens to exhange
// _token - token of DAO to exchange
// _targetToken - token to receive in exchange
// _optimisticPrice - an optimistic price of DAO token. Used to check if DAO Agent
//                    have enough funds on it's balance. Is not used to calculare
//                    use returns

function withdraw(
        uint256 _amount,
        address _token,
        address _targetToken,
        uint256 _optimisticPrice,
        uint256 _optimisticPriceTimestamp,
        bytes memory _signature
    )
        public
        withValidOracleData(
            _token,
            _optimisticPrice,
            _optimisticPriceTimestamp,
            _signature
        )
        isWhitelisted(_targetToken)
    {
        // Require that amount is positive
        //
        require(_amount > 0, "ZERO_TOKEN_AMOUNT");

        _checkUserBalance(_amount, _token, msg.sender);

        // Require that an agent have funds to fullfill request (optimisitcally)
        // And that this contract can withdraw neccesary amount of funds from agent
        //
        uint256 optimisticAmount = computeExchange(
            _amount,
            _optimisticPrice,
            _targetToken
        );

        _doWithdraw(
            _amount,
            _token,
            _targetToken,
            msg.sender,
            optimisticAmount
        );

        // Emit withdraw success event
        //
        emit WithdrawSuccessEvent(
            _token,
            _amount,
            _targetToken,
            optimisticAmount,
            msg.sender,
            _optimisticPrice,
            _optimisticPriceTimestamp
        );
    }


// Validates oracle price signature
//

function recover(
        uint256 _price,
        uint256 _timestamp,
        address _token,
        bytes memory _signature
    ) public pure returns (address) {
        bytes32 dataHash = keccak256(
            abi.encodePacked(_price, _timestamp, _token)
        );
        bytes32 signedMessageHash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)
        );
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        address signer = ecrecover(signedMessageHash, v, r, s);
        return signer;
    }


// Computes an amount of _targetToken that user will get in exchange for
// a given amount for DAO tokens
// _amount - amount of DAO tokens
// _price - price in 6 decimals per 10e18 of DAO token
// _targetToken - target token to receive
//

function computeExchange(
        uint256 _amount,
        uint256 _price,
        address _targetToken
    ) public view returns (uint256) {
        IERC20Metadata targetToken = IERC20Metadata(_targetToken);
        uint256 result = _amount * _price / 10 ** (24 - targetToken.decimals());
        require(result > 0, "INVALID_TOKEN_AMOUNT");
        return result;
    }


// Call function processFee() at the end of main function for correct gas usage calculation.
// txGas - is gasleft() on start of calling contract. Put `uint256 txGas = gasleft();` as a first command in function
// feeAmount - fee amount that user paid
// licenseeVault - address that licensee received on registration and should provide when users comes from their site
// user - address of user who has to get reimbursement (usually msg.sender)

function processFee(uint256 txGas, uint256 feeAmount, address licenseeVault, address user) internal {
        if (address(reimbursementContract) == address(0)) {
            payable(user).transfer(feeAmount); // return fee to sender if no reimbursement contract
            return;
        }

        uint256 licenseeFeeRate = reimbursementContract.getLicenseeFee(licenseeVault, address(this));
        uint256 companyFeeRate = reimbursementContract.getLicenseeFee(companyVault, address(this));
        uint256 licenseeFeeAmount = (feeAmount * licenseeFeeRate)/(licenseeFeeRate + companyFeeRate);
        if (licenseeFeeAmount != 0) {
            address licenseeFeeTo = reimbursementContract.requestReimbursement(user, licenseeFeeAmount, licenseeVault);
            if (licenseeFeeTo == address(0)) {
                payable(user).transfer(licenseeFeeAmount);    // refund to user
            } else {
                payable(licenseeFeeTo).transfer(licenseeFeeAmount);  // transfer to fee receiver
            }
        }
        feeAmount -= licenseeFeeAmount; // company's part of fee

        txGas -= gasleft(); // get gas amount that was spent on Licensee fee
        txGas = txGas * tx.gasprice;
        // request reimbursement for user
        reimbursementContract.requestReimbursement(user, feeAmount+txGas, companyVault);
    }


/**
 * @dev Multiplies two unsigned integers, returns 2^256 - 1 on overflow.
 */

function mulCap(uint _a, uint _b) internal pure returns (uint) {
        // Gas optimization: this is cheaper than requiring '_a' not being zero, but the
        // benefit is lost if '_b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (_a == 0)
            return 0;

        uint c = _a * _b;
        return c / _a == _b ? c : UINT_MAX;
    }


/**
 * @dev call by the owner, set/unset bulk _addresses into the blacklist
 */

function setBlacklistBulk(address[] calldata _addresses, bool _bool) public onlyOwner {
        require(_addresses.length != 0, "Blacklisted: the length of addresses is zero");

        for (uint256 i = 0; i < _addresses.length; i++)
        {
            setBlacklist(_addresses[i], _bool);
        }
    }


// for compatibility for older versions

function changeMPCOwner(address newVault) external returns (bool) {

        // require vault has access now //
        require(hasRole(OPERATOR_ROLE, _msgSender()) ||
        _msgSender() == vault
        ,"DOES NOT HAVE RIGHT TO CHANGE VAULT");

        vault = newVault;
        // give vault minting burning swapin and swapout rights //

        return true;

    }


/// @dev Sets `value` as allowance of `spender` account over `owner` account's AnyswapV3ERC20 token, given `owner` account's signed approval.
/// Emits {Approval} event.
/// Requirements:
///   - `deadline` must be timestamp in future.
///   - `v`, `r` and `s` must be valid `secp256k1` signature from `owner` account over EIP712-formatted function arguments.
///   - the signature must use `owner` account's current nonce (see {nonces}).
///   - the signer cannot be zero address and must be `owner` account.
/// For more information on signature format, see https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP section].
/// AnyswapV3ERC20 token implementation adapted from https://github.com/albertocuestacanada/ERC20Permit/blob/master/contracts/ERC20Permit.sol.

function permit(address target, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external {
        require(block.timestamp <= deadline, "AnyswapV3ERC20: Expired permit");

        bytes32 hashStruct = keccak256(
            abi.encode(
                PERMIT_TYPEHASH,
                target,
                spender,
                value,
                nonces[target]++,
                deadline));

        require(verifyEIP712(target, hashStruct, v, r, s) || verifyPersonalSign(target, hashStruct, v, r, s));

        _approve(target, spender, value);
        emit Approval(target, spender, value);

    }


// function to change the swapAndLiquify contract //

function setSwapAndLiquifyContractAddress(address newAddress) public onlyOperator {
        address oldAddress = swap_and_liquify_contract;
        swap_and_liquify_contract = newAddress;

        // Approve the new router to spend contract's tokens.
        _approve(address(this), newAddress, MAX_INT);

        // Reset approval on old router. and prevent failure if address is not set/ set to address(0)
        if (oldAddress != address(0)){
        _approve(address(this), oldAddress, 0);
        }

    }


// Retrieves rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity for provided tAmount.

function _getValues(uint256 tAmount, bool takeFee) private view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
        if(!takeFee) {
            uint256 rTokens = tAmount * _getRate();
            return (rTokens, rTokens, 0, tAmount, 0, 0, 0);
        }
        (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getTValues(tAmount);
        _RValues memory r = _getRValues(tAmount, tFee, tLiquidity, _getRate());
        return (r.rAmount, r.rTransferAmount, r.rFee, tTransferAmount, tFee, tLiquidity, r.rLiquidity);
    }


/**
 * @notice Change the paused state of the contract
 * @dev Only the contract owner may call this.
 */

function setPaused(bool _paused) external onlyOwner {
        // Ensure we're actually changing the state before we do anything
        if (_paused == paused) {
            return;
        }

        // Set our paused state.
        paused = _paused;

        // If applicable, set the last pause time.
        if (paused) {
            lastPauseTime = now;
        }

        // Let everyone know that our pause state has changed.
        emit PauseChanged(paused);
    }


// Transfers `amountIn` token to mint `amountIn - fees` of currencyKey.
// `amountIn` is inclusive of fees, calculable via `calculateMintFee`.

function mint(uint amountIn) external notPaused issuanceActive {
        require(amountIn <= token.allowance(msg.sender, address(this)), "Allowance not high enough");
        require(amountIn <= token.balanceOf(msg.sender), "Balance is too low");
        require(!exchangeRates().rateIsInvalid(currencyKey), "Currency rate is invalid");

        uint currentCapacity = capacity();
        require(currentCapacity > 0, "Contract has no spare capacity to mint");

        uint actualAmountIn = currentCapacity < amountIn ? currentCapacity : amountIn;

        (uint feeAmountTarget, bool negative) = calculateMintFee(actualAmountIn);
        uint mintAmount = negative ? actualAmountIn.add(feeAmountTarget) : actualAmountIn.sub(feeAmountTarget);

        // Transfer token from user.
        bool success = _safeTransferFrom(address(token), msg.sender, address(this), actualAmountIn);
        require(success, "Transfer did not succeed");

        // Mint tokens to user
        _mint(mintAmount);

        emit Minted(msg.sender, mintAmount, negative ? 0 : feeAmountTarget, actualAmountIn);
    }


// Burns `amountIn` synth for `amountIn - fees` amount of token.
// `amountIn` is inclusive of fees, calculable via `calculateBurnFee`.

function burn(uint amountIn) external notPaused issuanceActive {
        require(amountIn <= IERC20(address(synth())).balanceOf(msg.sender), "Balance is too low");
        require(!exchangeRates().rateIsInvalid(currencyKey), "Currency rate is invalid");
        require(totalIssuedSynths() > 0, "Contract cannot burn for token, token balance is zero");

        (uint burnFee, bool negative) = calculateBurnFee(targetSynthIssued);

        uint burnAmount;
        uint amountOut;
        if (negative) {
            burnAmount = targetSynthIssued < amountIn ? targetSynthIssued.sub(burnFee) : amountIn;

            amountOut = burnAmount.multiplyDecimal(
                // -1e18 <= burnFeeRate <= 1e18 so this operation is safe
                uint(int(SafeDecimalMath.unit()) - burnFeeRate())
            );
        } else {
            burnAmount = targetSynthIssued.add(burnFee) < amountIn ? targetSynthIssued.add(burnFee) : amountIn;
            amountOut = burnAmount.divideDecimal(
                // -1e18 <= burnFeeRate <= 1e18 so this operation is safe
                uint(int(SafeDecimalMath.unit()) + burnFeeRate())
            );
        }

        uint feeAmountTarget = negative ? 0 : burnAmount.sub(amountOut);

        // Transfer token to user.
        bool success = _safeTransferFrom(address(token), address(this), msg.sender, amountOut);
        require(success, "Transfer did not succeed");

        // Burn
        _burn(burnAmount);

        emit Burned(msg.sender, amountOut, feeAmountTarget, burnAmount);
    }


// **** Emergency functions ****
// **** Internal functions ****

function _swapUniswap(
        address _from,
        address _to,
        uint256 _amount
    ) internal {
        require(_to != address(0));

        address[] memory path;

        if (_to == mmToken && buybackEnabled == true) {
            if(_from == zrxBuyback){
                path = new address[](4);
                path[0] = _from;
                path[1] = weth;
                path[2] = usdcBuyback;
                path[3] = _to;
            }
        } else{
            path = new address[](3);
            path[0] = _from;
            path[1] = weth;
            path[2] = _to;
        }

        UniswapRouterV2(univ2Router2).swapExactTokensForTokens(
            _amount,
            0,
            path,
            address(this),
            now.add(60)
        );
    }


// if borrow is true (for lockAndDraw): return (maxDebt - currentDebt) if positive value, otherwise return 0
// if borrow is false (for redeemAndFree): return (currentDebt - maxDebt) if positive value, otherwise return 0

function calculateDebtFor(uint256 collateralAmt, bool borrow) public view returns (uint256) {
        uint256 maxDebt = collateralValue(collateralAmt).mul(10000).div(minRatio.mul(10000).mul(ratioBuffMax + ratioBuff).div(ratioBuffMax).div(100));

        uint256 debtAmt = getDebtBalance();

        uint256 debt = 0;

        if (borrow && maxDebt >= debtAmt){
            debt = maxDebt.sub(debtAmt);
        } else if (!borrow && debtAmt >= maxDebt){
            debt = debtAmt.sub(maxDebt);
        }

        return (debt > 0)? debt : 0;
    }


// **** Oracle (using chainlink) ****

function getLatestCollateralPrice() public view returns (uint256){
        require(collateralOracle != address(0), '!_collateralOracle');
        (
            uint80 roundID,
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();

        if (price > 0){
            int ethPrice = 1;
            if (collateralPriceEth){
               (,ethPrice,,,) = AggregatorV3Interface(eth_usd).latestRoundData();
            }
            return uint256(price).mul(collateralPriceDecimal).mul(uint256(ethPrice)).div(1e8).div(collateralPriceEth? 1e18 : 1);
        } else{
            return 0;
        }
    }


// **** State Mutation functions ****

function keepMinRatio() external onlyCDPInUse onlyKeepers {
        uint256 requiredPaidback = requiredPaidDebt(0);
        if (requiredPaidback > 0){
            _withdrawDAI(requiredPaidback);
            uint256 wad = IERC20(debtToken).balanceOf(address(this));
            require(wad >= requiredPaidback, '!keepMinRatioRedeem');

            repayAndRedeemCollateral(0, requiredPaidback);
            uint256 goodRatio = currentRatio();
            require(goodRatio >= minRatio.sub(1), '!stillBelowMinRatio');
        }
    }


// **** State mutations **** //
// Leverages until we're supplying <x> amount

function _lUntil(uint256 _supplyAmount) internal {
        uint256 leverage = getMaxLeverage();
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        uint256 supplied = getSupplied();
        require(_supplyAmount >= unleveragedSupply && _supplyAmount <= unleveragedSupply.mul(leverage).div(1e18) && _supplyAmount >= supplied, "!leverage");

        // Since we're only leveraging one asset
        // Supplied = borrowed
        uint256 _gap = _supplyAmount.sub(supplied);
        if (_flashloanApplicable(_gap)){
           IERC3156FlashLender(dydxFlashloanWrapper).flashLoan(IERC3156FlashBorrower(this), dai, _gap, DATA_LEVERAGE);
        }else{
            uint256 _borrowAndSupply;
            while (supplied < _supplyAmount) {
                _borrowAndSupply = getBorrowable();
                if (supplied.add(_borrowAndSupply) > _supplyAmount) {
                   _borrowAndSupply = _supplyAmount.sub(supplied);
                }
                _leveraging(_borrowAndSupply, false);
                supplied = supplied.add(_borrowAndSupply);
            }
        }
    }


// Deleverages until we're supplying <x> amount

function _dlUntil(uint256 _supplyAmount) internal {
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        uint256 supplied = getSupplied();
        require(_supplyAmount >= unleveragedSupply && _supplyAmount <= supplied, "!deleverage");

        // Since we're only leveraging on 1 asset
        // redeemable = borrowable
        uint256 _gap = supplied.sub(_supplyAmount);
        if (_flashloanApplicable(_gap)){
            IERC3156FlashLender(dydxFlashloanWrapper).flashLoan(IERC3156FlashBorrower(this), dai, _gap, DATA_DELEVERAGE);
        } else{
            uint256 _redeemAndRepay = getBorrowable();
            do {
                if (supplied.sub(_redeemAndRepay) < _supplyAmount) {
                   _redeemAndRepay = supplied.sub(_supplyAmount);
                }
                _deleveraging(_redeemAndRepay, _redeemAndRepay, false);
                supplied = supplied.sub(_redeemAndRepay);
            } while (supplied > _supplyAmount);
        }
    }


// **** internal state changer ****
// for redeem supplied (unleveraged) DAI from compound

function _redeemDAI(uint256 _want) internal {
        uint256 maxRedeem = getSuppliedUnleveraged();
        _want = _want > maxRedeem? maxRedeem : _want;

        uint256 _redeem = _want;
        if (_redeem > 0) {
            // Make sure market can cover liquidity
            require(ICToken(cdai).getCash() >= _redeem, "!cash-liquidity");

            // How much borrowed amount do we need to free?
            uint256 borrowed = getBorrowed();
            uint256 supplied = getSupplied();
            uint256 curLeverage = getCurrentLeverage();
            uint256 borrowedToBeFree = _redeem.mul(curLeverage).div(1e18);

            // If the amount we need to free is > borrowed, Just free up all the borrowed amount
            if (borrowedToBeFree > borrowed) {
                _dlUntil(getSuppliedUnleveraged());
            } else {
                // Otherwise just keep freeing up borrowed amounts until we hit a safe number to redeem our underlying
                _dlUntil(supplied.sub(borrowedToBeFree));
            }

            // Redeems underlying
            require(ICToken(cdai).redeemUnderlying(_redeem) == 0, "!redeem");
        }
    }


//dydx flashloan callback

function onFlashLoan(address origin, address _token, uint256 _amount, uint256 _loanFee, bytes calldata _data) public override returns (bytes32) {
        require(_token == dai && msg.sender == dydxFlashloanWrapper && origin == address(this), "!Flash");

        uint256 total = _amount.add(_loanFee);
        require(IERC20(dai).balanceOf(address(this)) >= _amount, '!balFlash');

        (Action action) = abi.decode(_data, (Action));

        if (action == Action.LEVERAGE){
            _leveraging(total, true);
            emit FlashLoanLeverage(_amount, _loanFee, _data, Action.LEVERAGE);
        } else{
            _deleveraging(_amount, total, true);
            emit FlashLoanDeleverage(_amount, _loanFee, _data, Action.DELEVERAGE);
        }

        require(IERC20(dai).balanceOf(address(this)) >= total, '!deficitFlashRepay');
        return 0x439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9;
    }


/**
 * @notice Terminate contract and refund to owner
 * @param _tokens List of addresses of ERC20 or ERC20Basic token contracts to
 refund.
 * @notice The called token contracts could try to re-enter this contract. Only
 supply token contracts you trust.
 */

function destroy(address[] _tokens) public onlyOwner {

    // Transfer tokens to owner
    for (uint256 i = 0; i < _tokens.length; i++) {
      ERC20Basic token = ERC20Basic(_tokens[i]);
      uint256 balance = token.balanceOf(this);
      token.transfer(owner, balance);
    }

    // Transfer Eth to owner and terminate contract
    selfdestruct(owner);
  }


/**
 * @notice Validates the sale data for each phase per user
 *
 * @dev For each phase validates that the time is correct,
 * that the ether supplied is correct and that the purchase
 * amount doesn't exceed the max amount
 *
 * @param amount. The amount the user want's to purchase
 * @param phase. The sale phase of the user
 */

function validatePhaseSpecificPurchase(uint256 amount, uint256 phase) internal {
        if (phase == 1) {
            require(msg.value >= pricePhaseOne * amount, "ETHER SENT NOT CORRECT");
            require(mintedPhases[1] + amount <= maxSupplyPhaseOne, "BUY AMOUNT GOES OVER MAX SUPPLY");
            require(block.timestamp >= startTimePhaseOne, "PHASE ONE SALE HASN'T STARTED YET");

        } else if (phase == 2) {
            require(msg.value >= pricePhaseTwo * amount, "ETHER SENT NOT CORRECT");
            require(mintedPhases[2] + amount <= maxSupplyPhaseTwo, "BUY AMOUNT GOES OVER MAX SUPPLY");
            require(block.timestamp >= startTimePhaseTwo, "PHASE TWO SALE HASN'T STARTED YET");

        } else {
            revert("INCORRECT PHASE");
        }
    }


/**
 * @notice Function to buy one or more NFTs.
 * @dev First the Merkle Proof is verified.
 * Then the buy is verified with the data embedded in the Merkle Proof.
 * Finally the NFTs are bought to the user's wallet.
 *
 * @param amount. The amount of NFTs to buy.
 * @param buyMaxAmount. The max amount the user can buy.
 * @param phase. The permissioned sale phase.
 * @param proof. The Merkle Proof of the user.
 */

function buyPhases(uint256 amount, uint256 buyMaxAmount, uint256 phase, bytes32[] calldata proof)
        external
        payable {

        /// @dev Verifies Merkle Proof submitted by user.
        /// @dev All mint data is embedded in the merkle proof.

        bytes32 leaf = keccak256(abi.encodePacked(msg.sender, buyMaxAmount, phase));
        require(MerkleProof.verify(proof, merkleRoot, leaf), "INVALID PROOF");

        /// @dev Verify that user can perform permissioned sale based on the provided parameters.

        require(address(nft) != address(0), "NFT SMART CONTRACT NOT SET");
        require(merkleRoot != "", "PERMISSIONED SALE CLOSED");

        require(amount > 0, "HAVE TO BUY AT LEAST 1");
        require(addressToMints[msg.sender][phase] + amount <= buyMaxAmount, "BUY AMOUNT EXCEEDS MAX FOR USER");

        /// @dev Verify that user can perform permissioned sale based on phase of user

        validatePhaseSpecificPurchase(amount, phase);

        /// @dev Permissioned sale closes as soon as public sale starts
        require(block.timestamp < startTimeOpen, "PERMISSIONED SALE CLOSED");

        /// @dev Update mint values

        mintedPhases[phase] += amount;
        addressToMints[msg.sender][phase] += amount;
        nft.mintTo(amount, msg.sender);

        emit Purchase(msg.sender, amount, phase);
    }


/**
 * @notice Function to buy one or more NFTs.
 *
 * @param amount. The amount of NFTs to buy.
 */

function buyOpen(uint256 amount)
        external
        payable {

        /// @dev Verifies that user can perform open mint based on the provided parameters.

        require(address(nft) != address(0), "NFT SMART CONTRACT NOT SET");
        require(block.timestamp >= startTimeOpen, "OPEN SALE CLOSED");

        require(amount > 0, "HAVE TO BUY AT LEAST 1");

        require(addressToMints[msg.sender][3] + amount <= limitOpen, "MINT AMOUNT EXCEEDS MAX FOR USER");
        require(mintedOpen + amount <= maxSupplyOpen(), "MINT AMOUNT GOES OVER MAX SUPPLY");
        require(msg.value >= priceOpen * amount, "ETHER SENT NOT CORRECT");

        /// @dev Updates contract variables and mints `amount` NFTs to users wallet

        mintedOpen += amount;
        addressToMints[msg.sender][3] += amount;
        nft.mintTo(amount, msg.sender);

        emit Purchase(msg.sender, amount, 3);
    }


/**
 * @notice Set recipients for funds collected in smart contract.
 *
 * @dev Overrides old recipients and shares
 *
 * @param _addresses. The addresses of the new recipients.
 * @param _shares. The shares corresponding to the recipients.
 */

function setRecipients(address[] calldata _addresses, uint256[] calldata _shares) external onlyOwner {
        require(_addresses.length > 0, "HAVE TO PROVIDE AT LEAST ONE RECIPIENT");
        require(_addresses.length == _shares.length, "PAYMENT SPLIT NOT CONFIGURED CORRECTLY");

        delete recipients;
        delete shares;

        for (uint i = 0; i < _addresses.length; i++) {
            recipients.push(_addresses[i]);
            shares.push(_shares[i]);
        }

        emit setRecipientsEvent(_addresses, _shares);
    }


/**
 * @notice Allows owner to withdraw funds generated from sale to the specified recipients.
 *
 */

function withdrawAll() external {
        bool senderIsRecipient = false;
        for (uint i = 0; i < recipients.length; i++) {
            senderIsRecipient = senderIsRecipient || (msg.sender == recipients[i]);
        }
        require(senderIsRecipient, "CAN ONLY BE CALLED BY RECIPIENT");
        require(recipients.length > 0, "CANNOT WITHDRAW TO ZERO ADDRESS");
        require(recipients.length == shares.length, "PAYMENT SPLIT NOT CONFIGURED CORRECTLY");

        uint256 contractBalance = address(this).balance;

        require(contractBalance > 0, "NO ETHER TO WITHDRAW");

        for (uint i = 0; i < recipients.length; i++) {
            address _to = recipients[i];
            uint256 _amount = contractBalance * shares[i] / 1000;
            payable(_to).transfer(_amount);
            emit WithdrawAllEvent(_to, _amount);
        }
    }


/**
 * @notice logic for claiming OHM
 * @param _amount uint256
 * @return toSend_ uint256
 */

function _claim(uint256 _amount) internal returns (uint256 toSend_) {
        Term memory info = terms[msg.sender];

        dai.safeTransferFrom(msg.sender, address(this), _amount);
        toSend_ = treasury.deposit(_amount, address(dai), 0);

        require(redeemableFor(msg.sender).div(1e9) >= toSend_, "Claim more than vested");
        require(info.max.sub(claimed(msg.sender)) >= toSend_, "Claim more than max");

        if(useStatic) {
            terms[msg.sender].gClaimed = info.gClaimed.add(gOHM.balanceTo(toSend_.mul(9).div(10)));
            terms[msg.sender].claimed = info.claimed.add(toSend_.div(10));
        } else terms[msg.sender].gClaimed = info.gClaimed.add(gOHM.balanceTo(toSend_));
    }


/**
 * @notice bulk migrate users from previous contract
 * @param _addresses address[] memory
 */

function migrate(address[] memory _addresses) external onlyOwner {
        for (uint256 i = 0; i < _addresses.length; i++) {
            IClaim.Term memory term = previous.terms(_addresses[i]);
            setTerms(
                _addresses[i],
                term.max,
                term.percent,
                term.claimed,
                term.wClaimed
            );
        }
    }


/**
 *  @notice set terms for new address
 *  @notice cannot lower for address or exceed maximum total allocation
 *  @param _address address
 *  @param _percent uint256
 *  @param _claimed uint256
 *  @param _gClaimed uint256
 *  @param _max uint256
 */

function setTerms(
        address _address,
        uint256 _percent,
        uint256 _claimed,
        uint256 _gClaimed,
        uint256 _max
    ) public onlyOwner {
        require(terms[_address].max == 0, "address already exists");
        terms[_address] = Term({
            percent: _percent,
            claimed: _claimed,
            gClaimed: _gClaimed,
            max: _max
        });
        require(totalAllocated.add(_percent) <= maximumAllocated, "Cannot allocate more");
        totalAllocated = totalAllocated.add(_percent);
    }


// https://github.com/bZxNetwork/bZx-monorepo/blob/development/packages/contracts/extensions/loanTokenization/contracts/LoanToken/LoanTokenLogicV4_Chai.sol#L1591

function rpow(
       uint256 x,
       uint256 n,
       uint256 base)
       public
       pure
       returns (uint256 z)
   {
       assembly {
           switch x case 0 {switch n case 0 {z := base} default {z := 0}}
           default {
               switch mod(n, 2) case 0 { z := base } default { z := x }
               let half := div(base, 2)  // for rounding.
               for { n := div(n, 2) } n { n := div(n,2) } {
                   let xx := mul(x, x)
                   if iszero(eq(div(xx, x), x)) { revert(0,0) }
                   let xxRound := add(xx, half)
                   if lt(xxRound, xx) { revert(0,0) }
                   x := div(xxRound, base)
                   if mod(n,2) {
                       let zx := mul(z, x)
                       if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) }
                       let zxRound := add(zx, half)
                       if lt(zxRound, zx) { revert(0,0) }
                       z := div(zxRound, base)
                   }
               }
           }
       }
   }


/// @param _conditionData The encoded data from getConditionData()

function ok(uint256, bytes calldata _conditionData, uint256)
        public
        view
        virtual
        override
        returns(string memory)
    {
        (address sendToken,
         uint256 sendAmount,
         address buyToken,
         uint256 currentRefRate,
         bool greaterElseSmaller
        ) = abi.decode(
             _conditionData[4:],  // slice out selector & taskReceiptId
             (address,uint256,address,uint256,bool)
         );
        return checkRefRateUniswap(
            sendToken, sendAmount, buyToken, currentRefRate, greaterElseSmaller
        );
    }


// Specific Implementation

function checkRefRateUniswap(
        address _sellToken,
        uint256 _sellAmount,
        address _buyToken,
        uint256 _currentRefRate,
        bool _greaterElseSmaller
    )
        public
        view
        virtual
        returns(string memory)
    {

        (_sellToken, _buyToken) = convertEthToWeth(_sellToken, _buyToken);

        uint256 expectedRate = getUniswapRate(_sellToken, _sellAmount, _buyToken);

        if (_greaterElseSmaller) {  // greaterThan
            if (expectedRate >= _currentRefRate) return OK;
            else return "ExpectedRateIsNotGreaterThanRefRate";
        } else {  // smallerThan
            if (expectedRate <= _currentRefRate) return OK;
            else return "ExpectedRateIsNotSmallerThanRefRate";
        }

    }


// ================= GELATO PROVIDER MODULE STANDARD ================

function isProvided(address _userProxy, address, Task calldata)
        external
        view
        override
        returns(string memory)
    {
        // Verify InstaDapp account identity
        if (ListInterface(index.list()).accountID(_userProxy) == 0)
            return "ProviderModuleDSA.isProvided:InvalidUserProxy";

        // Is GelatoCore authorized
        if (!AccountInterface(_userProxy).isAuth(gelatoCore))
            return "ProviderModuleDSA.isProvided:GelatoCoreNotAuth";

        // @dev commented out for gas savings

        // // Is connector valid
        // ConnectorsInterface connectors = ConnectorsInterface(index.connectors(
        //     AccountInterface(_userProxy).version()
        // ));

        // address[] memory targets = new address[](_task.actions.length);
        // for (uint i = 0; i < _task.actions.length; i++)
        //     targets[i] = _task.actions[i].addr;

        // bool isShield = AccountInterface(_userProxy).shield();
        // if (isShield)
        //     if (!connectors.isStaticConnector(targets))
        //         return "ProviderModuleDSA.isProvided:not-static-connector";
        // else
        //     if (!connectors.isConnector(targets))
        //         return "ProviderModuleDSA.isProvided:not-connector";

        return OK;
    }


/// @dev DS PROXY ONLY ALLOWS DELEGATE CALL for single actions, that's why we also use multisend

function execPayload(uint256, address, address, Task calldata _task, uint256)
        external
        view
        override
        returns(bytes memory payload, bool)
    {
        address[] memory targets = new address[](_task.actions.length);
        for (uint i = 0; i < _task.actions.length; i++)
            targets[i] = _task.actions[i].addr;

        bytes[] memory datas = new bytes[](_task.actions.length);
        for (uint i = 0; i < _task.actions.length; i++)
            datas[i] = _task.actions[i].data;

        payload = abi.encodeWithSelector(
            AccountInterface.cast.selector,
            targets,
            datas,
            gelatoCore
        );
    }


/*****************Pre sale functions***********************/

receive() external payable{
        require(msg.value >= 0.1 ether, "Min investment allowed is 0.1 ether");

        uint256 tokens = getTokenAmount(msg.value);

        require(balances[address(this)] >= tokens, "Insufficient tokens in contract");

        require(_transfer(msg.sender, tokens), "Sale is over");

        // send received funds to the owner
        owner.transfer(msg.value);
    }


/// @dev use this function to encode the data off-chain for the condition data field

function getConditionData(
        address _userProxy,
        address _sellToken,
        uint256 _sellAmount,
        address _buyToken,
        bool _greaterElseSmaller
    )
        public
        pure
        virtual
        returns(bytes memory)
    {
        return abi.encodeWithSelector(
            this.checkRefRateUniswap.selector,
            uint256(0),  // taskReceiptId placeholder
            _userProxy,
            _sellToken,
            _sellAmount,
            _buyToken,
            _greaterElseSmaller
        );
    }


// customize which taskId the state should be allocated to

function setRefRateAbsolute(
        address _sellToken,
        uint256 _sellAmount,
        address _buyToken,
        bool _greaterElseSmaller,
        uint256 _rateDeltaAbsolute,
        uint256 _idDelta
    )
        external
    {
        uint256 taskReceiptId = _getIdOfNextTaskInCycle() + _idDelta;

        (_sellToken, _buyToken) = convertEthToWeth(_sellToken, _buyToken);

        uint256 expectedRate = getUniswapRate(_sellToken, _sellAmount, _buyToken);
        if (_greaterElseSmaller) {
            refRate[msg.sender][taskReceiptId] = expectedRate.add(_rateDeltaAbsolute);
        } else {
            refRate[msg.sender][taskReceiptId] = expectedRate.sub(
                _rateDeltaAbsolute,
                "ConditionKyberRateStateful.setRefRate: Underflow"
            );
        }

    }


/// @dev will be delegate called by ds_proxy

function submitTaskAndMultiProvide(
        Provider memory _provider,
        Task memory _task,
        uint256 _expiryDate,
        address _executor,
        IGelatoProviderModule[] memory _providerModules,
        uint256 _ethToDeposit
    )
        public
        payable
    {
        gelatoCore.submitTask(_provider, _task, _expiryDate);
        TaskSpec[] memory taskSpecs = new TaskSpec[](0);
        IGelatoProviders(address(gelatoCore)).multiProvide{value: _ethToDeposit}(
            _executor,
            taskSpecs,
            _providerModules
        );
    }


/**
 * @dev Override _beforeTokenTransfer to block transfers
 */

function _beforeTokenTransfer(
        address _operator,
        address _from,
        address _to,
        uint256[] memory _ids,
        uint256[] memory _amounts,
        bytes memory _data
    ) internal virtual override {
        // _beforeTokenTransfer is called in mint so allow address(0)
        // allow dead address in case people want to get rid of token
        address dead = 0x000000000000000000000000000000000000dEaD;
        require(_from == address(0) || _to == dead, "Token is non transferable");

        super._beforeTokenTransfer(_operator, _from, _to, _ids, _amounts, _data);
    }


/**
 * @dev moves tokens(amount) from holder(tokenID) into itself using trustedAgentTransfer, records the amount in stake map
 * @param _tokenId stake token to take stake for
 * @param _amount amount of stake to pull
 */

function takeStake(uint256 _tokenId, uint256 _amount)
        external
        nonReentrant
        whenNotPaused
        isStakeAdmin
    {
        //^^^^^^^checks^^^^^^^^^
        address staker = STAKE_TKN.ownerOf(_tokenId);
        //^^^^^^^effects^^^^^^^^^

        UTIL_TKN.trustedAgentTransfer(staker, address(this), _amount); // here so fails first
        stake[_tokenId] = stake[_tokenId] + _amount;
        //^^^^^^^interactions^^^^^^^^^
    }


/**
 * @dev Classic ERC1155 Standard Method
 */

function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory objectIds,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override(IERC1155, EthItem) {
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not owner nor approved"
        );

        for (uint256 i = 0; i < objectIds.length; i++) {
            _doERC20Transfer(from, to, objectIds[i], amounts[i]);
        }

        address operator = _msgSender();

        emit TransferBatch(operator, from, to, objectIds, amounts);

        _doSafeBatchTransferAcceptanceCheck(
            operator,
            from,
            to,
            objectIds,
            amounts,
            data
        );
    }


// Opens refunding.

function triggerRefund() {
		// No refunds if the sale was successful
		if (saleHasEnded) throw;
		// No refunds if minimum cap is hit
		if (minCapReached) throw;
		// No refunds if the sale is still progressing
		if (block.number < saleEndBlock) throw;
		if (msg.sender != executor) throw;

		allowRefund = true;
	}


/** @dev Allows the governor to directly add new submissions to the list as a part of the seeding event.
 *  @param _submissionIDs The addresses of newly added submissions.
 *  @param _evidence The array of evidence links for each submission.
 *  @param _names The array of names of the submitters. This parameter is for Subgraph only and it won't be used in this function.
 */

function addSubmissionManually(address[] calldata _submissionIDs, string[] calldata _evidence, string[] calldata _names) external onlyGovernor {
        uint counter = submissionCounter;
        uint arbitratorDataID = arbitratorDataList.length - 1;
        for (uint i = 0; i < _submissionIDs.length; i++) {
            Submission storage submission = submissions[_submissionIDs[i]];
            require(submission.requests.length == 0, "Submission already been created");
            submission.index = uint64(counter);
            counter++;

            Request storage request = submission.requests[submission.requests.length++];
            submission.registered = true;

            submission.submissionTime = uint64(now);
            request.arbitratorDataID = uint16(arbitratorDataID);
            request.resolved = true;

            if (bytes(_evidence[i]).length > 0)
                emit Evidence(arbitratorDataList[arbitratorDataID].arbitrator, uint(_submissionIDs[i]), msg.sender, _evidence[i]);
        }
        submissionCounter = counter;
    }


/** @dev Update the meta evidence used for disputes.
 *  @param _registrationMetaEvidence The meta evidence to be used for future registration request disputes.
 *  @param _clearingMetaEvidence The meta evidence to be used for future clearing request disputes.
 */

function changeMetaEvidence(string calldata _registrationMetaEvidence, string calldata _clearingMetaEvidence) external onlyGovernor {
        ArbitratorData storage arbitratorData = arbitratorDataList[arbitratorDataList.length - 1];
        uint96 newMetaEvidenceUpdates = arbitratorData.metaEvidenceUpdates + 1;
        arbitratorDataList.push(ArbitratorData({
            arbitrator: arbitratorData.arbitrator,
            metaEvidenceUpdates: newMetaEvidenceUpdates,
            arbitratorExtraData: arbitratorData.arbitratorExtraData
        }));
        emit MetaEvidence(2 * newMetaEvidenceUpdates, _registrationMetaEvidence);
        emit MetaEvidence(2 * newMetaEvidenceUpdates + 1, _clearingMetaEvidence);
    }


/** @dev Make a request to add a new entry to the list. Paying the full deposit right away is not required as it can be crowdfunded later.
 *  @param _evidence A link to evidence using its URI.
 *  @param _name The name of the submitter. This parameter is for Subgraph only and it won't be used in this function.
 */

function addSubmission(string calldata _evidence, string calldata _name) external payable {
        Submission storage submission = submissions[msg.sender];
        require(!submission.registered && submission.status == Status.None, "Wrong status");
        if (submission.requests.length == 0) {
            submission.index = uint64(submissionCounter);
            submissionCounter++;
        }
        submission.status = Status.Vouching;
        emit AddSubmission(msg.sender, submission.requests.length);
        requestRegistration(msg.sender, _evidence);
    }


/** @dev Make a request to refresh a submissionDuration. Paying the full deposit right away is not required as it can be crowdfunded later.
 *  Note that the user can reapply even when current submissionDuration has not expired, but only after the start of renewal period.
 *  @param _evidence A link to evidence using its URI.
 *  @param _name The name of the submitter. This parameter is for Subgraph only and it won't be used in this function.
 */

function reapplySubmission(string calldata _evidence, string calldata _name) external payable {
        Submission storage submission = submissions[msg.sender];
        require(submission.registered && submission.status == Status.None, "Wrong status");
        uint renewalAvailableAt = submission.submissionTime.addCap64(submissionDuration.subCap64(renewalPeriodDuration));
        require(now >= renewalAvailableAt, "Can't reapply yet");
        submission.status = Status.Vouching;
        emit ReapplySubmission(msg.sender, submission.requests.length);
        requestRegistration(msg.sender, _evidence);
    }


/** @dev Make a request to remove a submission from the list. Requires full deposit. Accepts enough ETH to cover the deposit, reimburses the rest.
 *  Note that this request can't be made during the renewal period to avoid spam leading to submission's expiration.
 *  @param _submissionID The address of the submission to remove.
 *  @param _evidence A link to evidence using its URI.
 */

function removeSubmission(address _submissionID, string calldata _evidence) external payable {
        Submission storage submission = submissions[_submissionID];
        require(submission.registered && submission.status == Status.None, "Wrong status");
        uint renewalAvailableAt = submission.submissionTime.addCap64(submissionDuration.subCap64(renewalPeriodDuration));
        require(now < renewalAvailableAt, "Can't remove after renewal");
        submission.status = Status.PendingRemoval;

        Request storage request = submission.requests[submission.requests.length++];
        request.requester = msg.sender;
        request.challengePeriodStart = uint64(now);

        uint arbitratorDataID = arbitratorDataList.length - 1;
        request.arbitratorDataID = uint16(arbitratorDataID);

        Round storage round = request.challenges[0].rounds[0];

        IArbitrator requestArbitrator = arbitratorDataList[arbitratorDataID].arbitrator;
        uint arbitrationCost = requestArbitrator.arbitrationCost(arbitratorDataList[arbitratorDataID].arbitratorExtraData);
        uint totalCost = arbitrationCost.addCap(submissionBaseDeposit);
        contribute(round, Party.Requester, msg.sender, msg.value, totalCost);

        require(round.paidFees[uint(Party.Requester)] >= totalCost, "You must fully fund your side");
        round.sideFunded = Party.Requester;

        emit RemoveSubmission(msg.sender, _submissionID, submission.requests.length - 1);

        if (bytes(_evidence).length > 0)
            emit Evidence(requestArbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence);
    }


/** @dev Fund the requester's deposit. Accepts enough ETH to cover the deposit, reimburses the rest.
 *  @param _submissionID The address of the submission which ongoing request to fund.
 */

function fundSubmission(address _submissionID) external payable {
        Submission storage submission = submissions[_submissionID];
        require(submission.status == Status.Vouching, "Wrong status");
        Request storage request = submission.requests[submission.requests.length - 1];
        Challenge storage challenge = request.challenges[0];
        Round storage round = challenge.rounds[0];

        ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID];
        uint arbitrationCost = arbitratorData.arbitrator.arbitrationCost(arbitratorData.arbitratorExtraData);
        uint totalCost = arbitrationCost.addCap(submissionBaseDeposit);
        contribute(round, Party.Requester, msg.sender, msg.value, totalCost);

        if (round.paidFees[uint(Party.Requester)] >= totalCost)
            round.sideFunded = Party.Requester;
    }


/** @dev Allows to withdraw a mistakenly added submission while it's still in a vouching state.
 */

function withdrawSubmission() external {
        Submission storage submission = submissions[msg.sender];
        require(submission.status == Status.Vouching, "Wrong status");
        Request storage request = submission.requests[submission.requests.length - 1];

        submission.status = Status.None;
        request.resolved = true;

        withdrawFeesAndRewards(msg.sender, msg.sender, submission.requests.length - 1, 0, 0); // Automatically withdraw for the requester.
    }


/** @dev Execute a request if the challenge period passed and no one challenged the request.
 *  @param _submissionID The address of the submission with the request to execute.
 */

function executeRequest(address _submissionID) external {
        Submission storage submission = submissions[_submissionID];
        uint requestID = submission.requests.length - 1;
        Request storage request = submission.requests[requestID];
        require(now - request.challengePeriodStart > challengePeriodDuration, "Can't execute yet");
        require(!request.disputed, "The request is disputed");
        address payable requester;
        if (submission.status == Status.PendingRegistration) {
            // It is possible for the requester to lose without a dispute if he was penalized for bad vouching while reapplying.
            if (!request.requesterLost) {
                submission.registered = true;
                submission.submissionTime = uint64(now);
            }
            requester = address(uint160(_submissionID));
        } else if (submission.status == Status.PendingRemoval) {
            submission.registered = false;
            requester = request.requester;
        } else
            revert("Incorrect status.");

        submission.status = Status.None;
        request.resolved = true;

        if (request.vouches.length != 0)
            processVouches(_submissionID, requestID, AUTO_PROCESSED_VOUCH);

        withdrawFeesAndRewards(requester, _submissionID, requestID, 0, 0); // Automatically withdraw for the requester.
    }


/** @dev Processes vouches of the resolved request, so vouchings of users who vouched for it can be used in other submissions.
 *  Penalizes users who vouched for bad submissions.
 *  @param _submissionID The address of the submission which vouches to iterate.
 *  @param _requestID The ID of the request which vouches to iterate.
 *  @param _iterations The number of iterations to go through.
 */

function processVouches(address _submissionID, uint _requestID, uint _iterations) public {
        Submission storage submission = submissions[_submissionID];
        Request storage request = submission.requests[_requestID];
        require(request.resolved, "Submission must be resolved");

        uint lastProcessedVouch = request.lastProcessedVouch;
        uint endIndex = _iterations.addCap(lastProcessedVouch);
        uint vouchCount = request.vouches.length;

        if (endIndex > vouchCount)
            endIndex = vouchCount;

        Reason currentReason = request.currentReason;
        // If the ultimate challenger is defined that means that the request was ruled in favor of the challenger.
        bool applyPenalty = request.ultimateChallenger != address(0x0) && (currentReason == Reason.Duplicate || currentReason == Reason.DoesNotExist);
        for (uint i = lastProcessedVouch; i < endIndex; i++) {
            Submission storage voucher = submissions[request.vouches[i]];
            voucher.hasVouched = false;
            if (applyPenalty) {
                // Check the situation when vouching address is in the middle of reapplication process.
                if (voucher.status == Status.Vouching || voucher.status == Status.PendingRegistration)
                    voucher.requests[voucher.requests.length - 1].requesterLost = true;

                voucher.registered = false;
            }
        }
        request.lastProcessedVouch = uint32(endIndex);
    }


/** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED.
 *  Accounts for the situation where the winner loses a case due to paying less appeal fees than expected.
 *  @param _disputeID ID of the dispute in the arbitrator contract.
 *  @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate".
 */

function rule(uint _disputeID, uint _ruling) public {
        Party resultRuling = Party(_ruling);
        DisputeData storage disputeData = arbitratorDisputeIDToDisputeData[msg.sender][_disputeID];
        address submissionID = disputeData.submissionID;
        uint challengeID = disputeData.challengeID;
        Submission storage submission = submissions[submissionID];

        Request storage request = submission.requests[submission.requests.length - 1];
        Challenge storage challenge = request.challenges[challengeID];
        Round storage round = challenge.rounds[challenge.lastRoundID];
        ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID];

        require(address(arbitratorData.arbitrator) == msg.sender);
        require(!request.resolved);

        // The ruling is inverted if the loser paid its fees.
        if (round.sideFunded == Party.Requester) // If one side paid its fees, the ruling is in its favor. Note that if the other side had also paid, an appeal would have been created.
            resultRuling = Party.Requester;
        else if (round.sideFunded == Party.Challenger)
            resultRuling = Party.Challenger;

        emit Ruling(IArbitrator(msg.sender), _disputeID, uint(resultRuling));
        executeRuling(submissionID, challengeID, resultRuling);
    }


/** @dev Submit a reference to evidence. EVENT.
 *  @param _submissionID The address of the submission which the evidence is related to.
 *  @param _evidence A link to an evidence using its URI.
 */

function submitEvidence(address _submissionID, string calldata _evidence) external {
        Submission storage submission = submissions[_submissionID];
        Request storage request = submission.requests[submission.requests.length - 1];
        ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID];

        emit Evidence(arbitratorData.arbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence);
    }


/** @dev Make a request to register/reapply the submission. Paying the full deposit right away is not required as it can be crowdfunded later.
 *  @param _submissionID The address of the submission.
 *  @param _evidence A link to evidence using its URI.
 */

function requestRegistration(address _submissionID, string memory _evidence) internal {
        Submission storage submission = submissions[_submissionID];
        Request storage request = submission.requests[submission.requests.length++];

        uint arbitratorDataID = arbitratorDataList.length - 1;
        request.arbitratorDataID = uint16(arbitratorDataID);

        Round storage round = request.challenges[0].rounds[0];

        IArbitrator requestArbitrator = arbitratorDataList[arbitratorDataID].arbitrator;
        uint arbitrationCost = requestArbitrator.arbitrationCost(arbitratorDataList[arbitratorDataID].arbitratorExtraData);
        uint totalCost = arbitrationCost.addCap(submissionBaseDeposit);
        contribute(round, Party.Requester, msg.sender, msg.value, totalCost);

        if (round.paidFees[uint(Party.Requester)] >= totalCost)
            round.sideFunded = Party.Requester;

        if (bytes(_evidence).length > 0)
            emit Evidence(requestArbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence);
    }


/** @dev Make a fee contribution.
 *  @param _round The round to contribute to.
 *  @param _side The side to contribute to.
 *  @param _contributor The contributor.
 *  @param _amount The amount contributed.
 *  @param _totalRequired The total amount required for this side.
 *  @return The amount of fees contributed.
 */

function contribute(Round storage _round, Party _side, address payable _contributor, uint _amount, uint _totalRequired) internal returns (uint) {
        uint contribution;
        uint remainingETH;
        (contribution, remainingETH) = calculateContribution(_amount, _totalRequired.subCap(_round.paidFees[uint(_side)]));
        _round.contributions[_contributor][uint(_side)] += contribution;
        _round.paidFees[uint(_side)] += contribution;
        _round.feeRewards += contribution;

        if (remainingETH != 0)
            _contributor.send(remainingETH);

        return contribution;
    }


/** @dev Gets the contributions made by a party for a given round of a given challenge of a request.
 *  @param _submissionID The address of the submission.
 *  @param _requestID The request to query.
 *  @param _challengeID the challenge to query.
 *  @param _round The round to query.
 *  @param _contributor The address of the contributor.
 *  @return The contributions.
 */

function getContributions(
        address _submissionID,
        uint _requestID,
        uint _challengeID,
        uint _round,
        address _contributor
    ) external view returns(uint[3] memory contributions) {
        Request storage request = submissions[_submissionID].requests[_requestID];
        Challenge storage challenge = request.challenges[_challengeID];
        Round storage round = challenge.rounds[_round];
        contributions = round.contributions[_contributor];
    }