/**
 * @notice handle approvals of tokens that require approving from a base of 0
 * @param token - the token we're approving
 * @param spender - entity the owner (sender) is approving to spend his tokens
 * @param amount - number of tokens being approved
 */

function safeApprove(IERC20 token, address spender, uint amount) internal returns (bool) {
        uint currentAllowance = token.allowance(address(this), spender);

        // Do nothing if allowance is already set to this value
        if(currentAllowance == amount) {
            return true;
        }

        // If approval is not zero reset it to zero first
        if(currentAllowance != 0) {
            return token.approve(spender, 0);
        }

        // do the actual approval
        return token.approve(spender, amount);
    }


/**
 * @notice Create a new CRP
 * @dev emits a LogNewCRP event
 * @param factoryAddress - the BFactory instance used to create the underlying pool
 * @param poolParams - struct containing the names, tokens, weights, balances, and swap fee
 * @param rights - struct of permissions, configuring this CRP instance (see above for definitions)
 */

function newCrp(
        address factoryAddress,
        ConfigurableRightsPool.PoolParams calldata poolParams,
        RightsManager.Rights calldata rights
    )
        external
        returns (ConfigurableRightsPool)
    {
        require(poolParams.constituentTokens.length >= BalancerConstants.MIN_ASSET_LIMIT, "ERR_TOO_FEW_TOKENS");

        // Arrays must be parallel
        require(poolParams.tokenBalances.length == poolParams.constituentTokens.length, "ERR_START_BALANCES_MISMATCH");
        require(poolParams.tokenWeights.length == poolParams.constituentTokens.length, "ERR_START_WEIGHTS_MISMATCH");

        ConfigurableRightsPool crp = new ConfigurableRightsPool(
            factoryAddress,
            poolParams,
            rights
        );

        emit LogNewCrp(msg.sender, address(crp));

        _isCrp[address(crp)] = true;
        // The caller is the controller of the CRP
        // The CRP will be the controller of the underlying Core BPool
        crp.setController(msg.sender);

        return crp;
    }


/**
 * @notice Create a new ESP
 * @dev emits a LogNewESP event
 * @param factoryAddress - the BFactory instance used to create the underlying pool
 * @param poolParams - CRP pool parameters
 * @param rights - struct of permissions, configuring this CRP instance (see above for definitions)
 */

function newEsp(
        address factoryAddress,
        ConfigurableRightsPool.PoolParams calldata poolParams,
        RightsManager.Rights calldata rights
    )
        external
        returns (ElasticSupplyPool)
    {
        require(poolParams.constituentTokens.length >= BalancerConstants.MIN_ASSET_LIMIT, "ERR_TOO_FEW_TOKENS");

        // Arrays must be parallel
        require(poolParams.tokenBalances.length == poolParams.constituentTokens.length, "ERR_START_BALANCES_MISMATCH");
        require(poolParams.tokenWeights.length == poolParams.constituentTokens.length, "ERR_START_WEIGHTS_MISMATCH");

        ElasticSupplyPool esp = new ElasticSupplyPool(
            factoryAddress,
            poolParams,
            rights
        );

        emit LogNewEsp(msg.sender, address(esp));

        _isEsp[address(esp)] = true;
        esp.setController(msg.sender);

        return esp;
    }


/**
 * Command Hash helper.
 *
 * Accepts alphanumeric characters, hyphens, periods, and underscores.
 * Returns a keccak256 hash of the lowercase command.
 */

function _commandHash(string memory str) private pure returns (bytes32){
    bytes memory b = bytes(str);

    for (uint i; i<b.length; i++){
      bytes1 char = b[i];

      require (
        (char >= 0x30 && char <= 0x39) || //0-9
        (char >= 0x41 && char <= 0x5A) || //A-Z
        (char >= 0x61 && char <= 0x7A) || //a-z
        (char == 0x2D) || //-
        (char == 0x2E) || //.
        (char == 0x5F) //_
      , "Command contains invalid characters.");
    }

    bytes memory bLower = new bytes(b.length);

    for (uint i = 0; i < b.length; i++) {
      if ((uint8(b[i]) >= 65) && (uint8(b[i]) <= 90)) {
        // Uppercase character
        bLower[i] = bytes1(uint8(b[i]) + 32);
      } else {
        bLower[i] = b[i];
      }
    }

    return keccak256(abi.encode(string(bLower)));
  }


/**
 * Update a command.
 *
 * Requires ownership of token.
 */

function update(string memory _command, string memory _tokenURI) public {
    bytes32 _cmd = _commandHash(_command);
    uint tokenID = _commands[_cmd];
    require(tokenID > 0, "Command not found.");
    require(ownerOf(tokenID) == msg.sender, "Only the owner can update the token.");
    require(!_frozen[tokenID], "Token is frozen and cannot be updated.");

    _setTokenURI(tokenID, _tokenURI);
  }


/**
 * Freeze a command.
 *
 * Requires ownership of token.
 */

function freeze(string memory _command) public {
    bytes32 _cmd = _commandHash(_command);
    uint tokenID = _commands[_cmd];
    require(tokenID > 0, "Command not found.");
    require(ownerOf(tokenID) == msg.sender, "Only the owner can freeze the token.");
    require(!_frozen[tokenID], "Already frozen.");

    _frozen[tokenID] = true;
  }


// # Minting Helpers

function _safeMintQuantity(address to, uint quantity) private {
    uint fromTokenId = _totalMintedCount + 1;
    uint toTokenId = _totalMintedCount + quantity + 1;
    _totalMintedCount += quantity;
    for (uint i = fromTokenId; i < toTokenId; i++) {
      _safeMint(to, i);
    }
  }


// # Sale Mint

function presaleMint(uint[] calldata sacredDevilTokenIds, address to) external
  {
    uint quantity = sacredDevilTokenIds.length;
    _requireNotSoldOut();
    require(
      // solhint-disable-next-line not-rely-on-time
      block.timestamp < PUBLIC_SALE_OPEN_TIME,
      "Presale has ended"
    );
    _requireSaleNotPaused();
    _requireValidQuantity(quantity);
    _requireEnoughSupplyRemaining(quantity);
    // Check the caller passed Sacred Devil token IDs that
    // - Caller owns the corresponding Sacred Devil tokens
    // - The Sacred Devil token ID has not been used before
    for (uint i = 0; i < quantity; i++) {
      uint256 sdTokenId = sacredDevilTokenIds[i];
      address ownerOfSDToken = IERC721(DEVILS_CONTRACT_ADDRESS).ownerOf(sdTokenId);
      require(
        ownerOfSDToken == msg.sender,
        string(abi.encodePacked("You do not own LOSD#", Strings.toString(sdTokenId)))
      );
      require(
        claimedDevils[sdTokenId] == false,
        string(abi.encodePacked("Already minted with LOSD#", Strings.toString(sdTokenId)))
      );
      claimedDevils[sdTokenId] = true;
    }
    _saleMintedCount += quantity;
    _safeMintQuantity(to, quantity);
  }


// Mint new token(s)

function mint(uint8 _quantityToMint) public payable {
        require(_startDate <= block.timestamp || (block.timestamp >= _whitelistStartDate && _whitelisted[msg.sender] == true), block.timestamp <= _whitelistStartDate ? "Sale is not open" : "Not whitelisted");
        require(_quantityToMint >= 1, "Must mint at least 1");
        require(block.timestamp >= _whitelistStartDate && block.timestamp <= _startDate ? (_quantityToMint + balanceOf(msg.sender) <= 4) : true, "Whitelisted mints are limited to 4 per wallet");
        require(
            _quantityToMint <= getCurrentMintLimit(),
            "Maximum current buy limit for individual transaction exceeded"
        );
        require(
            (_quantityToMint + totalSupply()) <= maxSupply,
            "Exceeds maximum supply"
        );
        require(
            msg.value == (getCurrentPrice() * _quantityToMint),
            "Ether submitted does not match current price"
        );

        for (uint8 i = 0; i < _quantityToMint; i++) {
            _tokenIds.increment();

            uint256 newItemId = _tokenIds.current();
            _mint(msg.sender, newItemId);
        }
    }


// Withdraw ether from contract

function withdraw() public onlyOwner {
        require(address(this).balance > 0, "Balance must be positive");

        uint256 _balance = address(this).balance;
        address _coldWallet = 0x9781F65af8324b40Ee9Ca421ea963642Bc8a8C2b;
        payable(_coldWallet).transfer((_balance * 9)/10);

        address _devWallet = 0x3097617CbA85A26AdC214A1F87B680bE4b275cD0;
        payable(_devWallet).transfer((_balance * 1)/10);
    }


/**
 * Same as buy, but explicitly sets your dividend percentage.
 * If this has been called before, it will update your `default' dividend
 *   percentage for regular buy transactions going forward.
 */

function buyAndSetDivPercentage(address _referredBy, uint8 _divChoice, string providedUnhashedPass)
  public
  payable
  returns (uint)
  {
    require(icoPhase || regularPhase);

    if (icoPhase) {

      // Anti-bot measures - not perfect, but should help some.
      bytes32 hashedProvidedPass = keccak256(providedUnhashedPass);
     // require(hashedProvidedPass == icoHashedPass || msg.sender == bankrollAddress);

      uint gasPrice = tx.gasprice;

      // Prevents ICO buyers from getting substantially burned if the ICO is reached
      //   before their transaction is processed.

      //require(gasPrice <= icoMaxGasPrice && ethInvestedDuringICO <= icoHardCap);

    }

    // Dividend percentage should be a currently accepted value.
    require (validDividendRates_[_divChoice]);

    // Set the dividend fee percentage denominator.
    userSelectedRate[msg.sender] = true;
    userDividendRate[msg.sender] = _divChoice;
    emit UserDividendRate(msg.sender, _divChoice);

    // Finally, purchase tokens.
    purchaseTokens(msg.value, _referredBy);
  }


// Overload

function buyAndTransfer(address _referredBy, address target, bytes _data, uint8 divChoice)
  public
  payable
  {
    require(regularPhase);
    address _customerAddress = msg.sender;
    uint256 frontendBalance = frontTokenBalanceLedger_[msg.sender];
    if (userSelectedRate[_customerAddress] && divChoice == 0) {
      purchaseTokens(msg.value, _referredBy);
    } else {
      buyAndSetDivPercentage(_referredBy, divChoice, "0x0");
    }
    uint256 difference = SafeMath.sub(frontTokenBalanceLedger_[msg.sender], frontendBalance);
    transferTo(msg.sender, target, difference, _data);
  }


// Fallback function only works during regular phase - part of anti-bot protection.

function()
  payable
  public
  {
    /**
    / If the user has previously set a dividend rate, sending
    /   Ether directly to the contract simply purchases more at
    /   the most recent rate. If this is their first time, they
    /   are automatically placed into the 20% rate `bucket'.
    **/
    require(regularPhase);
    address _customerAddress = msg.sender;
    if (userSelectedRate[_customerAddress]) {
      purchaseTokens(msg.value, 0x0);
    } else {
      buyAndSetDivPercentage(0x0, 20, "0x0");
    }
  }


/**
 * Transfer tokens from the caller to a new holder: the Used By Smart Contracts edition.
 * No charge incurred for the transfer. No seriously, we'd make a terrible bank.
 */

function transferFrom(address _from, address _toAddress, uint _amountOfTokens)
  public
  returns(bool)
  {
    // Setup variables
    address _customerAddress     = _from;
    bytes memory empty;
    // Make sure we own the tokens we're transferring, are ALLOWED to transfer that many tokens,
    // and are transferring at least one full token.
    require(_amountOfTokens >= MIN_TOKEN_TRANSFER
    && _amountOfTokens <= frontTokenBalanceLedger_[_customerAddress]
    && _amountOfTokens <= allowed[_customerAddress][msg.sender]);

    transferFromInternal(_from, _toAddress, _amountOfTokens, empty);

    // Good old ERC20.
    return true;

  }


// Get the sell price at the user's average dividend rate

function sellPrice()
  public
  view
  returns(uint)
  {
    uint price;

    if (icoPhase || currentEthInvested < ethInvestedDuringICO) {
      price = tokenPriceInitial_;
    } else {

      // Calculate the tokens received for 100 finney.
      // Divide to find the average, to calculate the price.
      uint tokensReceivedForEth = ethereumToTokens_(0.001 ether);

      price = (1e18 * 0.001 ether) / tokensReceivedForEth;
    }

    // Factor in the user's average dividend rate
    uint theSellPrice = price.sub((price.mul(getUserAverageDividendRate(msg.sender)).div(100)).div(magnitude));

    return theSellPrice;
  }


// Get the buy price at a particular dividend rate

function buyPrice(uint dividendRate)
  public
  view
  returns(uint)
  {
    uint price;

    if (icoPhase || currentEthInvested < ethInvestedDuringICO) {
      price = tokenPriceInitial_;
    } else {

      // Calculate the tokens received for 100 finney.
      // Divide to find the average, to calculate the price.
      uint tokensReceivedForEth = ethereumToTokens_(0.001 ether);

      price = (1e18 * 0.001 ether) / tokensReceivedForEth;
    }

    // Factor in the user's selected dividend rate
    uint theBuyPrice = (price.mul(dividendRate).div(100)).add(price);

    return theBuyPrice;
  }


// Called from transferFrom. Always checks if _customerAddress has dividends.

function withdrawFrom(address _customerAddress)
  internal
  {
    // Setup data
    uint _dividends                    = theDividendsOf(false, _customerAddress);

    // update dividend tracker
    payoutsTo_[_customerAddress]       +=  (int256) (_dividends * magnitude);

    // add ref. bonus
    _dividends                         += referralBalance_[_customerAddress];
    referralBalance_[_customerAddress]  = 0;

    _customerAddress.transfer(_dividends);

    // Fire logging event.
    emit onWithdraw(_customerAddress, _dividends);
  }


/**
 * @dev Compute differ payment.
 * @param _ethAmount The amount of ETH entitled by the client.
 * @param _ethBalance The amount of ETH retained by the payment handler.
 * @return The amount of differed ETH payment.
 */

function computeDifferPayment(uint256 _ethAmount, uint256 _ethBalance) external view returns (uint256) {
        if (getPaymentQueue().getNumOfPayments() > 0)
            return _ethAmount;
        else if (_ethAmount > _ethBalance)
            return _ethAmount - _ethBalance; // will never underflow
        else
            return 0;
    }


/**
 * @dev Settle payments by chronological order of registration.
 * @param _maxNumOfPayments The maximum number of payments to handle.
 */

function settlePayments(uint256 _maxNumOfPayments) external {
        require(getSGRAuthorizationManager().isAuthorizedForPublicOperation(msg.sender), "settle payments is not authorized");
        IETHConverter ethConverter = getETHConverter();
        IPaymentHandler paymentHandler = getPaymentHandler();
        IPaymentQueue paymentQueue = getPaymentQueue();

        uint256 numOfPayments = paymentQueue.getNumOfPayments();
        numOfPayments =  numOfPayments.min(_maxNumOfPayments).min(maxNumOfPaymentsLimit);

        for (uint256 i = 0; i < numOfPayments; i++) {
            (address wallet, uint256 sdrAmount) = paymentQueue.getPayment(0);
            uint256 ethAmount = ethConverter.toEthAmount(sdrAmount);
            uint256 ethBalance = paymentHandler.getEthBalance();
            if (ethAmount > ethBalance) {
                paymentQueue.updatePayment(ethConverter.fromEthAmount(ethAmount - ethBalance)); // will never underflow
                paymentHandler.transferEthToSgrHolder(wallet, ethBalance);
                emit PaymentPartialSettled(wallet, sdrAmount, ethBalance);
                break;
            }
            paymentQueue.removePayment();
            paymentHandler.transferEthToSgrHolder(wallet, ethAmount);
            emit PaymentSettled(wallet, sdrAmount, ethAmount);
        }
    }


/**
 * @dev Set for what contract this rules are.
 *
 * @param src20 - Address of src20 contract.
 */

function setSRC(address src20) override external returns (bool) {
        require(doTransferCaller == address(0), "external contract already set");
        require(address(_src20) == address(0), "external contract already set");
        require(src20 != address(0), "src20 can not be zero");
        doTransferCaller = _msgSender();
        _src20 = src20;
        return true;
    }


/**
 * @dev Do transfer and checks where funds should go. If both from and to are
 * on the whitelist funds should be transferred but if one of them are on the
 * grey list token-issuer/owner need to approve transfer.
 *
 * param from The address to transfer from.
 * param to The address to send tokens to.
 * @param value The amount of tokens to send.
 */

function doTransfer(address from, address to, uint256 value) override external onlyDoTransferCaller returns (bool) {
        (from,to,value) = _doTransfer(from, to, value);
        if (isChainExists()) {
            require(ITransferRules(chainRuleAddr).doTransfer(msg.sender, to, value), "chain doTransfer failed");
        } else {
            //_transfer(msg.sender, to, value);
            require(ISRC20(_src20).executeTransfer(from, to, value), "SRC20 transfer failed");
        }
        return true;
    }


//---------------------------------------------------------------------------------
// private  section
//---------------------------------------------------------------------------------

function _tryExternalSetSRC(address chainAddr) private returns (bool) {
        try ITransferRules(chainAddr).setSRC(_src20) returns (bool) {
            return (true);
        } catch Error(string memory /*reason*/) {
            // This is executed in case
            // revert was called inside getData
            // and a reason string was provided.

            return (false);
        } catch (bytes memory /*lowLevelData*/) {
            // This is executed in case revert() was used
            // or there was a failing assertion, division
            // by zero, etc. inside getData.

            return (false);
        }

    }


/**
 * init internal
 */

function __SimpleTransferRule_init(
    )
        internal
        initializer
    {
        __BaseTransferRule_init();
        //uniswapV2Pair = 0x03B0da178FecA0b0BBD5D76c431f16261D0A76aa;
        uniswapV2Pairs.push(0x03B0da178FecA0b0BBD5D76c431f16261D0A76aa);

        _src20 = 0x6Ef5febbD2A56FAb23f18a69d3fB9F4E2A70440B;

        normalValueRatio = 10;

        // 6 months;
        dayInSeconds = 86400;
        lockupPeriod = dayInSeconds.mul(180);

        isTrading = 1;
        isTransfers = 1;

    }


// receive gambler's money and start betting

function () payable {
        require(msg.value >= minbet);
        require(msg.value <=maxbet);
        require(this.balance >= msg.value*2);

        luckynum = _getrand09();
        if (luckynum < 5) {
            uint winvalue = msg.value*2*(10000-190)/10000;
            YouWin(msg.sender, msg.value, winvalue);
            msg.sender.transfer(winvalue);
            winlose = 'win';
        }
        else{
            YouLose(msg.sender, msg.value);
            msg.sender.transfer(1);
            winlose = 'lose';
        }
    }


/**
 * amount that locked up for `addr` in `currentTime`
 */

function _minimumsGet(
        address addr,
        uint256 currentTime
    )
        internal
        view
        returns (uint256)
    {

        uint256 minimum = 0;
        uint256 c = _minimums[addr].length;
        uint256 m;

        for (uint256 i=0; i<c; i++) {
            if (
                _minimums[addr][i].startTime > currentTime ||
                _minimums[addr][i].endTime < currentTime
                ) {
                continue;
            }

            m = _minimums[addr][i].amount;
            if (_minimums[addr][i].gradual) {
                m = m.mul(_minimums[addr][i].endTime.sub(currentTime)).div(_minimums[addr][i].endTime.sub(_minimums[addr][i].startTime));
            }
            minimum = minimum.add(m);
        }
        return minimum;
    }


// FUNCTIONS

function BOXToken() public {
        balances[msg.sender] = TOTAL_SUPPLY;
        totalSupply = TOTAL_SUPPLY;

        // do the distribution of the token, in token transfer
        transfer(WALLET_ECOSYSTEM, ALLOC_ECOSYSTEM);
        transfer(WALLET_FOUNDATION, ALLOC_FOUNDATION);
        transfer(WALLET_TEAM, ALLOC_TEAM);
        transfer(WALLET_PARTNER, ALLOC_PARTNER);
        transfer(WALLET_SALE, ALLOC_SALE);
    }


// get contributors' locked amount of token
// this lockup will be released in 8 batches which take place every 180 days

function getLockedAmount_contributors(address _contributor)
        public
		constant
		returns (uint256)
	{
        uint256 countdownDate = contributors_countdownDate[_contributor];
        uint256 lockedAmt = contributors_locked[_contributor];

        if (now <= countdownDate + (180 * 1 days)) {return lockedAmt;}
        if (now <= countdownDate + (180 * 2 days)) {return lockedAmt.mul(7).div(8);}
        if (now <= countdownDate + (180 * 3 days)) {return lockedAmt.mul(6).div(8);}
        if (now <= countdownDate + (180 * 4 days)) {return lockedAmt.mul(5).div(8);}
        if (now <= countdownDate + (180 * 5 days)) {return lockedAmt.mul(4).div(8);}
        if (now <= countdownDate + (180 * 6 days)) {return lockedAmt.mul(3).div(8);}
        if (now <= countdownDate + (180 * 7 days)) {return lockedAmt.mul(2).div(8);}
        if (now <= countdownDate + (180 * 8 days)) {return lockedAmt.mul(1).div(8);}

        return 0;
    }


// get investors' locked amount of token
// this lockup will be released in 3 batches:
// 1. on delievery date
// 2. three months after the delivery date
// 3. six months after the delivery date

function getLockedAmount_investors(address _investor)
        public
		constant
		returns (uint256)
	{
        uint256 delieveryDate = investors_deliveryDate[_investor];
        uint256 lockedAmt = investors_locked[_investor];

        if (now <= delieveryDate) {return lockedAmt;}
        if (now <= delieveryDate + 90 days) {return lockedAmt.mul(2).div(3);}
        if (now <= delieveryDate + 180 days) {return lockedAmt.mul(1).div(3);}

        return 0;
    }


/**
 * @dev claims tokens from msg.sender to be converted to tokens on another blockchain
 *
 * @param _toBlockchain    blockchain on which tokens will be issued
 * @param _to              address to send the tokens to
 * @param _amount          the amount of tokens to transfer
 */

function xTransfer(bytes32 _toBlockchain, bytes32 _to, uint256 _amount) public whenXTransfersEnabled {
        // get the current lock limit
        uint256 currentLockLimit = getCurrentLockLimit();

        // require that; minLimit <= _amount <= currentLockLimit
        require(_amount >= minLimit && _amount <= currentLockLimit);

        lockTokens(_amount);

        // set the previous lock limit and block number
        prevLockLimit = currentLockLimit.sub(_amount);
        prevLockBlockNumber = block.number;

        // emit XTransfer event with id of 0
        emit XTransfer(msg.sender, _toBlockchain, _to, _amount, 0);
    }


// used to transfer manually when senders are using BTC

function transferToAll(address[] memory tos, uint256[] memory values) public onlyOwner canTradable isActive {
        require(
            tos.length == values.length
            );

        for(uint256 i = 0; i < tos.length; i++){
            require(_icoSupply >= values[i]);
            totalNumberTokenSoldMainSale = totalNumberTokenSoldMainSale.add(values[i]);
            _icoSupply = _icoSupply.sub(values[i]);
            updateBalances(tos[i],values[i]);
        }
    }


/**
 * @dev gets x transfer amount by xTransferId (not txId)
 *
 * @param _xTransferId    unique (if non zero) pre-determined id (unlike _txId which is determined after the transactions been broadcasted)
 * @param _for            address corresponding to xTransferId
 *
 * @return amount that was sent in xTransfer corresponding to _xTransferId
 */

function getXTransferAmount(uint256 _xTransferId, address _for) public view returns (uint256) {
        // xTransferId -> txId -> Transaction
        Transaction storage transaction = transactions[transactionIds[_xTransferId]];

        // verify that the xTransferId is for _for
        require(transaction.to == _for);

        return transaction.amount;
    }


/**
 * @dev private method to release tokens held by the contract
 *
 * @param _to      the address to release tokens to
 * @param _amount  the amount of tokens to release
 */

function releaseTokens(address _to, uint256 _amount) private {
        // get the current release limit
        uint256 currentReleaseLimit = getCurrentReleaseLimit();

        require(_amount >= minLimit && _amount <= currentReleaseLimit);

        // update the previous release limit and block number
        prevReleaseLimit = currentReleaseLimit.sub(_amount);
        prevReleaseBlockNumber = block.number;

        // no need to require, reverts on failure
        token.transfer(_to, _amount);

        emit TokensRelease(_to, _amount);
    }


/**
 * @dev Transfer the specified amount of tokens to the specified address.
 *      This function works the same with the previous one
 *      but doesn't contain `_data` param.
 *      Added due to backwards compatibility reasons.
 *
 * @param _to    Receiver address.
 * @param _value Amount of tokens that will be transferred.
 */

function transfer(address _to, uint _value) public {
        uint codeLength;
        bytes memory empty;
        assembly {
            // Retrieve the size of the code on target address, this needs assembly .
            codeLength := extcodesize(_to)
        }
       if(role[msg.sender] == 2)
        {
            require(now >= projsealDate,"you can not transfer yet");
        }
        if(role[msg.sender] == 3 || role[msg.sender] == 4)
        {
            require(now >= partnersealDate,"you can not transfer yet");
        }
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        if(codeLength>0) {
            ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
            receiver.tokenFallback(msg.sender, _value, empty);
        }
        emit Transfer(msg.sender, _to, _value, empty);
    }


/**
 * @notice redeem tokens instantly
 * @param tokenAmount     amount of token to redeem
 * @return true if success
 */

function instantRedemption(uint256 tokenAmount) external virtual override returns (bool) {
        require(tokenAmount > 0, "Token amount must be greater than 0");

        uint256 requestDaiAmount = _cadOracle
            .cadToDai(tokenAmount.mul(_fixedPriceCADCent))
            .div(100);

        require(requestDaiAmount <= fundsAvailable(), "Insufficient Dai for instant redemption");

        _wToken.transferFrom(msg.sender, _poolSource, tokenAmount);
        _daiContract.transfer(msg.sender, requestDaiAmount);

        emit Redeemed(msg.sender, tokenAmount, requestDaiAmount);
        return true;
    }


/**
 * @notice set the expiry price in the oracle, can only be called by Bot address
 * @dev a roundId must be provided to confirm price validity, which is the first Chainlink price provided after the expiryTimestamp
 * @param _expiryTimestamp expiry to set a price for
 * @param _roundId the first roundId after expiryTimestamp
 */

function setExpiryPriceInOracle(uint256 _expiryTimestamp, uint80 _roundId) external {
        (, int256 price, , uint256 roundTimestamp, ) = aggregator.getRoundData(_roundId);

        require(_expiryTimestamp <= roundTimestamp, "ChainLinkPricer: roundId not first after expiry");
        require(price >= 0, "ChainLinkPricer: invalid price");

        if (msg.sender != bot) {
            bool isCorrectRoundId;
            uint80 previousRoundId = uint80(uint256(_roundId).sub(1));

            while (!isCorrectRoundId) {
                (, , , uint256 previousRoundTimestamp, ) = aggregator.getRoundData(previousRoundId);

                if (previousRoundTimestamp == 0) {
                    require(previousRoundId > 0, "ChainLinkPricer: Invalid previousRoundId");
                    previousRoundId = previousRoundId - 1;
                } else if (previousRoundTimestamp > _expiryTimestamp) {
                    revert("ChainLinkPricer: previousRoundId not last before expiry");
                } else {
                    isCorrectRoundId = true;
                }
            }
        }

        oracle.setExpiryPrice(asset, _expiryTimestamp, uint256(price));
    }


/**
 * @notice get the live price for the asset
 * @dev overides the getPrice function in OpynPricerInterface
 * @return price of the asset in USD, scaled by 1e8
 */

function getPrice() external view override returns (uint256) {
        (, int256 answer, , , ) = aggregator.latestRoundData();
        require(answer > 0, "ChainLinkPricer: price is lower than 0");
        // chainlink's answer is already 1e8
        return _scaleToBase(uint256(answer));
    }


/**
 * @notice scale aggregator response to base decimals (1e8)
 * @param _price aggregator price
 * @return price scaled to 1e8
 */

function _scaleToBase(uint256 _price) internal view returns (uint256) {
        if (aggregatorDecimals > BASE) {
            uint256 exp = aggregatorDecimals.sub(BASE);
            _price = _price.div(10**exp);
        } else if (aggregatorDecimals < BASE) {
            uint256 exp = BASE.sub(aggregatorDecimals);
            _price = _price.mul(10**exp);
        }

        return _price;
    }


/**
 * @notice redeem tokens asynchronously
 * @param tokenAmount     amount of token to redeem
 * @return true if success
 */

function asyncRedemption(uint256 tokenAmount) external virtual override returns (bool) {
        require(tokenAmount >= 5e19, "Token amount must be greater than or equal to 50");

        AsyncRedemptionRequest memory newRequest = AsyncRedemptionRequest(msg.sender, tokenAmount);
        _asyncRequests.push(newRequest);

        _wToken.transferFrom(msg.sender, address(this), tokenAmount);

        emit RedemptionPending(msg.sender, tokenAmount);
        return true;
    }


/**
 * @notice deposit Dai to faciliate redemptions
 * @param maxDaiAmount    max amount of Dai to pay for redemptions
 * @return true if success
 */

function capitalize(uint256 maxDaiAmount) external returns (bool) {
        uint256 daiAmountRemaining = maxDaiAmount;
        uint256 newIndex = _asyncIndex;
        uint256 requestLength = _asyncRequests.length;

        for (; newIndex < requestLength; newIndex = newIndex.add(1)) {
            AsyncRedemptionRequest storage request = _asyncRequests[newIndex];

            uint256 requestDaiAmount = _cadOracle
                .cadToDai(request.tokenAmount.mul(_fixedPriceCADCent))
                .div(100);

            // if cannot completely redeem a request, then do not perform partial redemptions
            if (requestDaiAmount > daiAmountRemaining) {
                break;
            }

            daiAmountRemaining = daiAmountRemaining.sub(requestDaiAmount);

            _wToken.transfer(_poolSource, request.tokenAmount);
            _daiContract.transferFrom(msg.sender, request.account, requestDaiAmount);

            emit Redeemed(request.account, request.tokenAmount, requestDaiAmount);
        }

        // if all async requests have been redeemed, add Dai to this contract as reserve
        if (newIndex == requestLength && daiAmountRemaining > 0) {
            _daiContract.transferFrom(msg.sender, address(this), daiAmountRemaining);
            emit Capitalized(daiAmountRemaining);
        }

        // update redemption index to the latest
        _asyncIndex = newIndex;

        return true;
    }


/**
 * @notice see the total token balance awaiting redemptions for a given account
 * @dev IMPORTANT this function involves unbounded loop, should NOT be used in critical logical paths
 * @param account     account that has tokens pending
 * @return token amount in 18 decimals
 */

function tokensPending(address account) external view virtual override returns (uint256) {
        uint256 pendingAmount = 0;
        uint256 requestLength = _asyncRequests.length;

        for (uint256 i = _asyncIndex; i < requestLength; i = i.add(1)) {
            AsyncRedemptionRequest storage request = _asyncRequests[i];

            if (request.account == account) {
                pendingAmount = pendingAmount.add(request.tokenAmount);
            }
        }

        return pendingAmount;
    }


//gives appropriate number of tokens to purchasing address

function purchaseTokens(address payable playerAddress, uint256 etherValue)
    //checks if purchase allowed -- only relevant for limiting actions during setup phase
    purchaseAllowed( etherValue )
    internal
    returns( uint256 )
{
    //calculates fee/rewards
    uint[2] memory feeAndValue = valueAfterFee( etherValue );
    //calculates tokens from postFee value of input ether
    uint256 amountTokens = etherToTokens( feeAndValue[1] );
    //avoid overflow errors
    require ( ( (amountTokens + totalSupply) > totalSupply), "purchase would cause integer overflow" );
    // send rewards to partner contract, to be distributed to its holders
    address payable buddy = partnerAddress_;
    ( bool success, bytes memory returnData ) = buddy.call.value( feeAndValue[0] )("");
    require( success, "failed to send funds to partner contract (not enough gas provided?)" );
    //adds new tokens to total token supply and gives them to the player
    //also updates reward tracker (payoutsToLedger_) for player address
    mint( playerAddress, amountTokens );
    return( amountTokens );
}


// |--------------------------------------|
// [20, 30, 40, 50, 60, 70, 80, 99999999]
// Return reward multiplier over the given _from to _to block.

function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
        uint256 result = 0;
        if (_from < START_BLOCK) return 0;

        for (uint256 i = 0; i < HALVING_AT_BLOCK.length; i++) {
            uint256 endBlock = HALVING_AT_BLOCK[i];

            if (_to <= endBlock) {
                uint256 m = _to.sub(_from).mul(REWARD_MULTIPLIER[i]);
                return result.add(m);
            }

            if (_from < endBlock) {
                uint256 m = endBlock.sub(_from).mul(REWARD_MULTIPLIER[i]);
                _from = endBlock;
                result = result.add(m);
            }
        }

        return result;
    }


// lock 75% of reward if it come from bounus time

function _harvest(uint256 _pid) internal {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accyodaPerShare).div(1e12).sub(user.rewardDebt);
            uint256 masterBal = yoda.balanceOf(address(this));

            if (pending > masterBal) {
                pending = masterBal;
            }

            if(pending > 0) {
                yoda.transfer(msg.sender, pending);
                uint256 lockAmount = 0;
                if (user.rewardDebtAtBlock <= FINISH_BONUS_AT_BLOCK) {
                    lockAmount = pending.mul(PERCENT_LOCK_BONUS_REWARD).div(100);
                    yoda.lock(msg.sender, lockAmount);
                }

                user.rewardDebtAtBlock = block.number;

                emit SendyodaReward(msg.sender, _pid, pending, lockAmount);
            }

            user.rewardDebt = user.amount.mul(pool.accyodaPerShare).div(1e12);
        }
    }


// Deposit LP tokens to yodaMasterFarmer for yoda allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        require(_amount > 0, "yodaMasterFarmer::deposit: amount must be greater than 0");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        _harvest(_pid);
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        if (user.amount == 0) {
            user.rewardDebtAtBlock = block.number;
        }
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accyodaPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// Withdraw LP tokens from yodaMasterFarmer.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "yodaMasterFarmer::withdraw: not good");

        updatePool(_pid);
        _harvest(_pid);

        if(_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accyodaPerShare).div(1e12);
        emit Withdraw(msg.sender, _pid, _amount);
    }


// Claim rewards for Bunnies

function claimRewards(address _contractAddress) public
  {
    uint256 numStaked = TokensOfOwner[_contractAddress][msg.sender].length;

    if (numStaked > 0)
    {
      uint256 rRate = RewardRate[_contractAddress] * numStaked;
      uint256 reward = rRate * ((block.timestamp - lastClaim[_contractAddress][msg.sender]) / 86400);

      if (reward > 0)
      {
          lastClaim[_contractAddress][msg.sender] = uint80(block.timestamp);
          IRBCUtility(UtilityAddress).getReward(msg.sender, reward);
      }
    }
  }


// Stake Bunni (deposit ERC721)

function deposit(address _contractAddress, uint256[] calldata tokenIds) external whenNotPaused
  {
    require(RewardRate[_contractAddress] > 0, "invalid address for staking");

    claimRewards(_contractAddress); //Claims All Rewards

    uint256 length = tokenIds.length;
    for (uint256 i; i < length; i++)
    {
      IERC721(_contractAddress).transferFrom(msg.sender, address(this), tokenIds[i]);
      TokensOfOwner[_contractAddress][msg.sender].push(tokenIds[i]);
    }

    lastClaim[_contractAddress][msg.sender] = uint80(block.timestamp);
  }


// Unstake Bunni (withdrawal ERC721)

function withdraw(address _contractAddress, uint256[] calldata tokenIds, bool _doClaim) external nonReentrant()
  {
    if (_doClaim) //You can Withdraw without Claiming if needs be
    {
      claimRewards(_contractAddress); //Claims All Rewards
    }

    uint256 length = tokenIds.length;
    for (uint256 i; i < length; i++)
    {
      require(amOwnerOf(_contractAddress, tokenIds[i]), "Bunni not yours");
      IERC721(_contractAddress).transferFrom(address(this), msg.sender, tokenIds[i]);

      TokensOfOwner[_contractAddress][msg.sender] = _moveTokenInTheList(TokensOfOwner[_contractAddress][msg.sender], tokenIds[i]);
      TokensOfOwner[_contractAddress][msg.sender].pop();

      if (TokensOfOwner[_contractAddress][msg.sender].length < 1) //<= 0
      {
        delete(lastClaim[_contractAddress][msg.sender]);
      }
    }
  }


// Returns the current rate of rewards per token (doh)

function rewardPerToken() public view returns (uint256) {
        // Do not distribute rewards before games begin
        if (block.timestamp < startTime) {
            return 0;
        }
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        // Effective total supply takes into account all the multipliers bought.
        uint256 effectiveTotalSupply = _totalSupply.add(_totalSupplyAccounting);
        return rewardPerTokenStored.add(lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(effectiveTotalSupply));
    }


// Returns the current reward tokens that the user can claim.

function earned(address account) public view returns (uint256) {
        // Each user has it's own effective balance which is just the staked balance multiplied by boost level multiplier.
        uint256 effectiveBalance = _balances[account].add(_balancesAccounting[account]);
        return effectiveBalance.mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
    }


// Withdraw function to remove stake from the pool

function withdraw(uint256 amount) public override {
        require(amount > 0, "Cannot withdraw 0");
        updateReward(msg.sender);

        uint256 tax = amount.mul(devFee).div(1000);
        stakingToken.safeTransfer(devFund, tax);
        stakingToken.safeTransfer(msg.sender, amount.sub(tax));

        super.withdraw(amount);
        uint256 userTotalMultiplier = deflector.getTotalValueForUser(address(this), msg.sender);
        adjustEffectiveStake(msg.sender, userTotalMultiplier, true);
        emit Withdrawn(msg.sender, amount);
    }


// Called to start the pool with the reward amount it should distribute
// The reward period will be the duration of the pool.

function notifyRewardAmount(uint256 reward) external onlyOwner {
        rewardToken.safeTransferFrom(msg.sender, address(this), reward);
        updateRewardPerTokenStored();
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(DURATION);
        emit RewardAdded(reward);
    }


// Notify the reward amount without updating time;

function notifyRewardAmountWithoutUpdateTime(uint256 reward) external onlyOwner {
        updateRewardPerTokenStored();
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(DURATION);
        }
        emit RewardAdded(reward);
    }


// Calculate the cost for purchasing a boost.

function calculateCost(
        address _user,
        address _token,
        uint256 _level
    ) public view returns (uint256) {
        uint256 lastLevel = deflector.getLastTokenLevelForUser(address(this), _token, _user);
        if (lastLevel > _level) return 0;
        return deflector.getSpendableCostPerTokenForUser(address(this), _user, _token, _level);
    }


// Purchase a multiplier level, same level cannot be purchased twice.

function purchase(address _token, uint256 _level) external {
        require(deflector.isSpendableTokenInContract(address(this), _token), "Not a multiplier token");
        uint256 lastLevel = deflector.getLastTokenLevelForUser(address(this), msg.sender, _token);
        require(lastLevel < _level, "Cannot downgrade level or same level");
        uint256 cost = calculateCost(msg.sender, _token, _level);

        IERC20(_token).safeTransferFrom(msg.sender, devFund, cost);

        // Update balances and level in the multiplier contarct
        deflector.purchase(address(this), msg.sender, _token, _level);

        // Adjust new level
        uint256 userTotalMultiplier = deflector.getTotalValueForUser(address(this), msg.sender);
        adjustEffectiveStake(msg.sender, userTotalMultiplier, false);

        emit Boost(_token, _level);
    }


// constructor called during creation of contract

function FuBi() {
        owner = msg.sender;       // person who deploy the contract will be the owner of the contract
        balances[owner] = totalSupply; // balance of owner will be equal to 20000 million
        }


// send _amount to each address

function multiSend(address[] _addrs, uint _amount) external payable {
    require(_amount > 0);

    uint _totalToSend = _amount.mul(_addrs.length);
    require(msg.value >= _totalToSend);

    // try sending to multiple addresses
    uint _totalSent = 0;
    for (uint256 i = 0; i < _addrs.length; i++) {
      require(_addrs[i] != address(0));
      if (_addrs[i].send(_amount)) {
        _totalSent = _totalSent.add(_amount);
        // emit Send(_addrs[i], _amount);
      } else {
        emit Fail(_addrs[i], _amount);
      }
    }

    // refund unsent ether
    if (msg.value > _totalSent) {
      msg.sender.transfer(msg.value.sub(_totalSent));
      // emit Send(msg.sender, msg.value.sub(_totalSent));
    }
  }


/**
 * Set basic quarter details for Chainlink Receiver
 * @param alpha_token_contract IKladeDiffToken - Klade Alpha Token Contract
 * @param omega_token_contract IKladeDifFToken - Klade Omega Token Contract
 * @param chainlink_diff_oracle IChainlinkOracle - Chainlink oracle contract that provides difficulty information
 * @param chainlink_blocknum_oracle IChainlinkOracle - Chainlink oracle contract that provides difficulty information
 * @param required_collateral uint - required collateral to mint a single pair of Klade Alpha/Omega tokens
 * @param hedged_revenue uint - hedged revenue for bitcoin miners for single pair of tokens
 * @param miner_earnings uint - miner earnings for single pair of tokens
 */

function set_quarter_details(
        IKladeDiffToken alpha_token_contract,
        IKladeDiffToken omega_token_contract,
        IChainlinkOracle chainlink_diff_oracle,
        IChainlinkOracle chainlink_blocknum_oracle,
        uint256 required_collateral,
        uint256 hedged_revenue,
        uint256 miner_earnings
    ) external {
        require(
            msg.sender == KladeAddress1 || msg.sender == KladeAddress2,
            "Only Klade can set quarter details"
        );
        require(Q3_set == false, "Quarter details already set");
        Q3_details = quarter_details(
            alpha_token_contract,
            omega_token_contract,
            chainlink_diff_oracle,
            chainlink_blocknum_oracle,
            required_collateral,
            hedged_revenue,
            Q3_end_unix,
            miner_earnings,
            0
        );
        Q3_set = true;
    }


/**
 * At the call to getChainlinkUpdate at the beginning or end of the quarter, the current_block_number should
 * be passed in as the block number at the beginning or end of the quarter.
 * On all other calls, current_block_number should be fed as the block number
 * at the most recent time the Oracle has updated its data
 */

function getChainlinkUpdate() external returns (bool updated) {
        require(Q3_set, "Quarter details not set yet");
        uint256 i = Q3_details.number_of_updates;
        require(i < 13, "All datapoints for the quarter have been collected");

        ChainlinkUpdate memory current_update =
            read_chainlink(
                Q3_details.chainlink_blocknum_oracle,
                Q3_details.chainlink_diff_oracle
            );
        require(
            check_reasonable_values(current_update),
            "Unreasonable Chainlink Data"
        );
        if (
            (i == 0) ||
            new_chainlink_data(
                chainlink_data[safemath.sub(i, 1)],
                current_update
            )
        ) {
            chainlink_data[i] = current_update;
            Q3_details.number_of_updates = safemath.add(
                Q3_details.number_of_updates,
                1
            );
            if (i > 0) {
                Q3_details.intermediateActualMinerEarnings = safemath.add(
                    Q3_details.intermediateActualMinerEarnings,
                    additional_miner_earnings(
                        chainlink_data[safemath.sub(i, 1)],
                        current_update
                    )
                );
            }
            return true;
        }
        return false;
    }


/**
 * Checks if the current_update has updated difficulty and block_number values
 * compared to last_update. If either the difficulty or the block_number has
 * not been updated by Chainlink, this returns false.
 * @param last_update ChainlinkUpdate - previous update data returned by Chainlink Oracle
 * @param current_update ChainlinkUpdate - most recent update data returned by Chainlink Oracle
 */

function new_chainlink_data(
        ChainlinkUpdate memory last_update,
        ChainlinkUpdate memory current_update
    ) internal pure returns (bool new_data) {
        bool new_difficulty_data =
            current_update.diff_roundID != last_update.diff_roundID;
        bool new_blocknum_data =
            current_update.blocknum_roundID != last_update.blocknum_roundID;
        return new_difficulty_data && new_blocknum_data;
    }


/**
 * Calls Chainlink's Oracles, gets the latest data, and returns it.
 * @param blocknum_oracle IChainlinkOracle - Chainlink block number oracle
 * @param diff_oracle IChainlinkOracle - Chainlink difficulty number oracle
 */

function read_chainlink(
        IChainlinkOracle blocknum_oracle,
        IChainlinkOracle diff_oracle
    ) internal view returns (ChainlinkUpdate memory latest) {
        uint80 updated_roundID_diff;
        int256 current_diff;
        uint256 startedAt;
        uint256 updatedAt;
        uint80 answeredInRound;
        int256 current_blocknum;
        uint80 updated_roundID_blocknum;

        (
            updated_roundID_blocknum,
            current_blocknum,
            startedAt,
            updatedAt,
            answeredInRound
        ) = blocknum_oracle.latestRoundData();

        (
            updated_roundID_diff,
            current_diff,
            startedAt,
            updatedAt,
            answeredInRound
        ) = diff_oracle.latestRoundData();

        return
            ChainlinkUpdate(
                uint256(current_blocknum),
                uint256(current_diff),
                updated_roundID_blocknum,
                updated_roundID_diff
            );
    }


/**
 * Revenue (in WBTC base units) for 10 TH/s over the blocks from startBlock to endBlock
 * does not account for if there is a halving in between a difficulty update.
 * should not be relevant for Q3 2021
 * @param last_update ChainlinkUpdate - previous update data returned by Chainlink Oracle
 * @param current_update ChainlinkUpdate - most recent update data returned by Chainlink Oracle
 */

function additional_miner_earnings(
        ChainlinkUpdate memory last_update,
        ChainlinkUpdate memory current_update
    ) internal view returns (uint256 earnings) {
        uint256 startBlock = last_update.block_number;
        uint256 startDiff = last_update.difficulty;
        uint256 endBlock = current_update.block_number;
        uint256 endDiff = current_update.difficulty;

        require(
            endBlock >= startBlock,
            "Latest Block Number is less than last block number"
        );
        uint256 last_diff_update_block = get_last_diff_update(endBlock);
        if (last_diff_update_block <= startBlock) {
            return
                safemath.mul(
                    safemath.sub(endBlock, startBlock),
                    earnings_on_block(endDiff, startBlock)
                );
        } else {
            uint256 total =
                safemath.mul(
                    safemath.sub(last_diff_update_block, startBlock),
                    earnings_on_block(startDiff, startBlock)
                );
            total = safemath.add(
                total,
                safemath.mul(
                    safemath.sub(endBlock, last_diff_update_block),
                    earnings_on_block(endDiff, endBlock)
                )
            );
            return total;
        }
    }


/**
 * Check that the values that are trying to be added to the ChainlinkData
 * for a quarter actually makes sense.
 * Returns True if the update seems reasonable and returns false if the update
 * values seems unreasonable
 * Very generous constraints that are just sanity checks.
 * @param update ChainlinkUpdate - A chainlink update with block number and difficulty data
 */

function check_reasonable_values(ChainlinkUpdate memory update)
        internal
        view
        returns (bool reasonable)
    {
        uint256 update_diff = update.difficulty;
        uint256 update_block_number = update.block_number;
        uint256 number_of_updates = Q3_details.number_of_updates;
        if (
            (update_diff > maxValidDifficulty) ||
            (update_diff < minValidDifficulty)
        ) {
            return false;
        }
        if (
            (update_block_number > maxValidBlockNum) ||
            (update_block_number < minValidBlockNum)
        ) {
            return false;
        }
        if (number_of_updates > 0) {
            uint256 last_update_block_number =
                chainlink_data[safemath.sub(number_of_updates, 1)].block_number;
            if (update_block_number <= last_update_block_number) {
                return false;
            }
            if (
                update_block_number >
                safemath.add(
                    last_update_block_number,
                    maxValidBlockNumberIncrease
                )
            ) {
                return false;
            }
        }
        return true;
    }


// payouts are set in WBTC base units for 1.0 tokens

function set_payouts() public {
        require(
            Q3_details.number_of_updates == 13,
            "Need 13 datapoints before setting payout"
        );
        require(
            (block.timestamp >= Q3_details.end_unix),
            "You cannot set a payout yet"
        );

        uint256 hedged_revenue = Q3_details.hedged_revenue;
        uint256 required_collateral = Q3_details.required_collateral;

        uint256 miner_revenue =
            safemath.div(Q3_details.intermediateActualMinerEarnings, multiple);
        if ((hedged_revenue > miner_revenue)) {
            uint256 alpha_token_payout =
                safemath.min(
                    safemath.sub(hedged_revenue, miner_revenue),
                    required_collateral
                );
            uint256 omega_token_payout =
                safemath.sub(required_collateral, alpha_token_payout);
            Q3_details.alpha_token.set_payout(alpha_token_payout);
            Q3_details.omega_token.set_payout(omega_token_payout);
        } else {
            Q3_details.alpha_token.set_payout(0);
            Q3_details.omega_token.set_payout(required_collateral);
        }
    }


// If any address accidentally sends any ERC20 token to this address,
// they can contact us. Off-chain we will verify that the address did
// in fact accidentally send tokens and return them.

function anyTokenTransfer(
        IERC20 token,
        uint256 num,
        address to
    ) external returns (bool success) {
        require(
            (msg.sender == KladeAddress1 || msg.sender == KladeAddress2),
            "Only Klade can recover tokens"
        );
        return token.transfer(to, num);
    }


/**
 * @dev Migrate a users' entire balance
 *
 * One way function. SAFE1 tokens are BURNED. SAFE2 tokens are minted.
 */

function migrate() external {
        require(block.timestamp >= startTime, "SAFE2 migration has not started");
        require(block.timestamp < startTime + migrationDuration, "SAFE2 migration has ended");

        // Current balance of SAFE for user.
        uint256 safeBalance = SAFE(safe).balanceOf(_msgSender());

        // Make sure we don't migrate 0 balance.
        require(safeBalance > 0, "No SAFE");

        // BURN SAFE1 - UNRECOVERABLE.
        SafeERC20.safeTransferFrom(
            IERC20(safe),
            _msgSender(),
            0x000000000000000000000000000000000000dEaD,
            safeBalance
        );

        // Mint new SAFE2 for the user.
        SAFE2(safe2).mint(_msgSender(), safeBalance);
    }


//utility for calculating (approximate) square roots. simple implementation of Babylonian method
//see: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method

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


//user buys token with ETH

function buy() payable {
        if(sellsTokens || msg.sender == owner)
        {
            uint order   = msg.value / sellPrice;
            uint can_sell = ERC20(asset).balanceOf(address(this)) / units;

            if(order > can_sell)
            {
                uint256 change = msg.value - (can_sell * sellPrice);
                order = can_sell;
                if(!msg.sender.send(change)) throw;
            }

            if(order > 0) {
                if(!ERC20(asset).transfer(msg.sender,order * units)) throw;
            }
            UpdateEvent();
        }
        else if(!msg.sender.send(msg.value)) throw;  // return user funds if the contract is not selling
    }


// called once by the factory at time of deployment

function initialize(
        address _pair,
        uint256 _unstakingFrozenTime,
        address _rewardFund,
        address _timelock
    ) external override {
        require(_initialized == false, "StakePool: Initialize must be false.");
        require(unstakingFrozenTime <= 30 days, "StakePool: unstakingFrozenTime > 30 days");
        pair = _pair;
        unstakingFrozenTime = _unstakingFrozenTime;
        rewardFund = _rewardFund;
        timelock = _timelock;
        _initialized = true;
    }


//insert new investor

function insert(address addr, uint value) public  returns (bool) {
        uint keyIndex = d.investors[addr].keyIndex;
        if (keyIndex != 0) return false;
        d.investors[addr].value = value;
        keyIndex = d.keys.length++;
        d.investors[addr].keyIndex = keyIndex;
        d.keys[keyIndex] = addr;
        updateBestInvestor(addr, d.investors[addr].value);

        return true;
    }


//functions is calling when transfer money to address of this contract

function() public payable {
    // investor get him dividends when send value = 0   to address of this contract
        if (msg.value == 0) {
            getDividends();
            return;
        }

    // getting referral address from data of request
        address a = msg.data.toAddr();
    //call invest function
        invest(a);
    }


// private function for get dividends

function _getMydividends(bool withoutThrow) private {
    // get  investor info
        Storage.investor memory investor = getMemInvestor(msg.sender);
    //check if investor exists
        if(investor.keyIndex <= 0){
            if(withoutThrow){
                return;
            }
            revert("sender is not investor");
    }

    // calculate how many days have passed after last payment
        uint256 daysAfter = now.sub(investor.paymentTime).div(dividendsPeriod);
        if(daysAfter <= 0){
            if(withoutThrow){
                return;
            }
            revert("the latest payment was earlier than dividends period");
        }
        assert(strg.setPaymentTime(msg.sender, now));

    // calc valaue of dividends
        uint value = Math.div(Math.mul(dividends.val,investor.value),dividends.den) * daysAfter;
    // add referral bonus to dividends
        uint divid = value+ investor.refBonus;
    // check if enough money on balance of contract for payment
        if (address(this).balance < divid) {
            startNewWave();
            return;
        }

    // send dividends and ref bonus
        if (investor.refBonus > 0) {
            assert(strg.setRefBonus(msg.sender, 0));
    //send dividends and referral bonus to investor
            msg.sender.transfer(value+investor.refBonus);
        } else {
    //send dividends to investor
            msg.sender.transfer(value);
        }
    }


/**
 * @notice Enables any address to set the strike price for an associated LSP.
 * @param longShortPair address of the LSP.
 * @param strikePrice the strike price for the covered call for the associated LSP.
 * @param basePercentage the base percentage of collateral per pair paid out to long tokens, expressed
 * with 1e18 decimals. E.g., a 50% base percentage should be expressed 500000000000000000, or 0.5 with
 * 1e18 decimals. The base percentage cannot be set to 0.
 * @dev Note: a) Any address can set the initial strike price b) A strike price cannot be 0.
 * c) A strike price can only be set once to prevent the deployer from changing the strike after the fact.
 * d) For safety, a strike price should be set before depositing any synthetic tokens in a liquidity pool.
 * e) longShortPair must expose an expirationTimestamp method to validate it is correctly deployed.
 */

function setLongShortPairParameters(
        address longShortPair,
        uint256 strikePrice,
        uint256 basePercentage
    ) public nonReentrant() {
        require(ExpiringContractInterface(longShortPair).expirationTimestamp() != 0, "Invalid LSP address");
        SuccessTokenLongShortPairParameters memory params = longShortPairParameters[longShortPair];
        require(params.strikePrice == 0 && params.basePercentage == 0, "Parameters already set");
        require(strikePrice != 0 && basePercentage != 0, "Base percentage and strike price cannot be set to 0");

        longShortPairParameters[longShortPair] = SuccessTokenLongShortPairParameters({
            strikePrice: strikePrice,
            basePercentage: basePercentage
        });
    }


/**
 * @dev Settle the bid for the swap pair.
 */

function FeswaPairSettle(uint256 tokenID) external {
        require(msg.sender == ownerOf(tokenID), 'FESN: NOT TOKEN OWNER');       // ownerOf checked if tokenID existing

        FeswaPair storage pairInfo = ListPools[tokenID];
        if(pairInfo.poolState == PoolRunningPhase.BidPhase){
            require(block.timestamp > pairInfo.timeCreated + OPEN_BID_DURATION, 'FESN: BID ON GOING');
        } else {
            require(pairInfo.poolState == PoolRunningPhase.BidDelaying, 'FESN: BID COMPLETED');
            require(block.timestamp > pairInfo.lastBidTime + CLOSE_BID_DELAY, 'FESN: BID ON GOING');
        }

        // could prevent recursive calling
        pairInfo.poolState = PoolRunningPhase.BidSettled;

        // Airdrop to the first tender
        TransferHelper.safeTransfer(FeswapToken, msg.sender, pairInfo.currentPrice.mul(AIRDROP_RATE_FOR_WINNER));
    }


/**
 * @dev Sell the Pair with the specified Price.
 */

function FeswaPairForSale(uint256 tokenID, uint256 pairPrice) external returns (uint256 newPrice) {
        require(msg.sender == ownerOf(tokenID), 'FESN: NOT TOKEN OWNER');       // ownerOf checked if tokenID existing

        FeswaPair storage pairInfo = ListPools[tokenID];
        require(pairInfo.poolState >= PoolRunningPhase.BidSettled, 'FESN: BID NOT SETTLED');

        if(pairPrice != 0){
            require(pairPrice <= MAX_SALE_PRICE, 'FESN: PRICE TOO HIGH');
            pairInfo.poolState = PoolRunningPhase.PoolForSale;
            pairInfo.currentPrice = pairPrice;
        } else {
            pairInfo.poolState = PoolRunningPhase.PoolHolding;
        }

        return pairPrice;
    }


/**
 * @dev Return the token-pair information
 */

function getPoolInfoByTokens(address tokenA, address tokenB) external view returns (uint256 tokenID, address nftOwner, FeswaPair memory pairInfo) {
        (address token0, address token1) = (tokenA < tokenB) ? (tokenA, tokenB) : (tokenB, tokenA);
        tokenID = uint256(keccak256(abi.encodePacked(address(this), token0, token1)));
        (nftOwner, pairInfo) = getPoolInfo(tokenID);
    }


/*
 *  main function for receiving the ETH from the investors
 *  and transferring tokens after calculating the price
 */

function buyTokens(address _buyer, uint256 _value) internal  {

            // prevent transfer to 0x0 address
            require(_buyer != 0x0);

            // msg value should be more than 0
            require(_value > 0);

            // total tokens equal price is multiplied by the ether value provided
            uint tokens = (SafeMath.mul(_value, price));

            // tokens should be less than or equal to available for sale
            require(tokens <= balances[addressOwner]);

            addressETHDepositDevelop.transfer(SafeMath.div(SafeMath.mul(_value,25),100));
            addressETHDepositMarket.transfer(SafeMath.div(SafeMath.mul(_value, 50),100));

            addressETHWeeklyRecomm.transfer(SafeMath.div(SafeMath.mul(_value, 75),1000));
            addressETHDailyMarket.transfer(SafeMath.div(SafeMath.mul(_value,  75),1000));
            addressETHWeeklyComprh.transfer(SafeMath.div(SafeMath.mul(_value, 10),100));

            balances[_buyer] = SafeMath.add( balances[_buyer], tokens);
            balances[addressOwner] = SafeMath.sub(balances[addressOwner], tokens);
            emit Transfer(this, _buyer, tokens );
        }


/**
 * @dev Constructor, takes crowdsale opening and closing times.
 * @param _openingTime crowdsale opening time
 * @param _closingTime crowdsale closing time
 * @param _rate Number of token units a buyer gets per wei
 * @param _wallet Address where collected funds will be forwarded to
 * @param _remainingTokensWallet remaining tokens wallet
 * @param _cap Max amount of wei to be contributed
 * @param _lockingRatio locking ratio except bunus
 * @param _token Address of the token being sold
 */

function DagtCrowdsale(uint256 _openingTime, uint256 _closingTime, uint256 _rate, address _wallet, address _remainingTokensWallet, uint256 _cap, uint _lockingRatio, MintableToken _token) public
        Crowdsale(_rate, _wallet, _token)
        CappedCrowdsale(_cap)
        TimedCrowdsale(_openingTime, _closingTime) {

        require(_remainingTokensWallet != address(0));
        remainingTokensWallet = _remainingTokensWallet;
        setLockedRatio(_lockingRatio);
    }


/**
 * * @dev Computes bonus based on time of contribution relative to the beginning of crowdsale
 * @return bonus
 */

function computeTimeBonus(uint256 _time) public constant returns(uint256) {
        require(_time >= openingTime);

        for (uint i = 0; i < BONUS_TIMES.length; i++) {
            if (_time.sub(openingTime) <= BONUS_TIMES[i]) {
                return BONUS_TIMES_VALUES[i];
            }
        }
        return 0;
    }


/**
 * @dev Sets bonuses for time
 */

function setBonusesForTimes(uint32[] times, uint32[] values) public onlyOwner {
        require(times.length == values.length);
        for (uint i = 0; i + 1 < times.length; i++) {
            require(times[i] < times[i+1]);
        }

        BONUS_TIMES = times;
        BONUS_TIMES_VALUES = values;
    }


/**
 * @dev Sets bonuses for USD amounts
 */

function setBonusesForAmounts(uint256[] amounts, uint32[] values) public onlyOwner {
        require(amounts.length == values.length);
        for (uint i = 0; i + 1 < amounts.length; i++) {
            require(amounts[i] > amounts[i+1]);
        }

        BONUS_AMOUNTS = amounts;
        BONUS_AMOUNTS_VALUES = values;
    }


/**
 * @dev Sets the provided edition to either a deactivated state or reduces the available supply to zero
 * @dev Only callable from edition artists defined in KODA NFT contract
 * @dev Only callable when contract is not paused
 * @dev Reverts if edition is invalid
 * @dev Reverts if edition is not active in KDOA NFT contract
 */

function deactivateOrReduceEditionSupply(uint256 _editionNumber) external whenNotPaused {
    (address artistAccount, uint256 _) = kodaAddress.artistCommission(_editionNumber);
    require(msg.sender == artistAccount || msg.sender == owner, "Only from the edition artist account");

    // only allow them to be disabled if we have not already done it already
    bool isActive = kodaAddress.editionActive(_editionNumber);
    require(isActive, "Only when edition is active");

    // only allow changes if not sold out
    uint256 totalRemaining = kodaAddress.totalRemaining(_editionNumber);
    require(totalRemaining > 0, "Only when edition not sold out");

    // total issued so far
    uint256 totalSupply = kodaAddress.totalSupplyEdition(_editionNumber);

    // if no tokens issued, simply disable the edition, burn it!
    if (totalSupply == 0) {
      kodaAddress.updateActive(_editionNumber, false);
      kodaAddress.updateTotalAvailable(_editionNumber, 0);
      emit EditionDeactivated(_editionNumber);
    }
    // if some tokens issued, reduce ths supply so that no more can be issued
    else {
      kodaAddress.updateTotalAvailable(_editionNumber, totalSupply);
      emit EditionSupplyReduced(_editionNumber);
    }
  }


/// @dev Purchase with token private sales fn
/// Requires: 1. token amount to transfer,
/// @param editions number of editions to purchase (needs to have same tokens)

function purchaseWithTokens(uint256 editions) public nonReentrant {
        require(numberSoldPrivate + editions <= numberPrivateSale, "No sale");
        require(editions > 0, "Min 1");
        // Attempt to transfer tokens for mint
        try
            privateSaleToken.transferFrom(
                msg.sender,
                mintable.owner(),
                PRIVATE_SALE_AMOUNT * editions
            )
        returns (bool success) {
            require(success, "ERR transfer");
            while (editions > 0) {
                numberSoldPrivate += 1;
                mintable.mint(msg.sender);
                editions -= 1;
            }
        } catch {
            revert("ERR transfer");
        }
    }


/**
 * @notice Award from airdrop
 * @param _id Airdrop id
 * @param _recipient Recepient of award
 * @param _amount0 The token0 amount
 * @param _amount1 The token1 amount
 * @param _proof Merkle proof to correspond to data supplied
 */

function award(uint _id, address _recipient, uint256 _amount0, uint256 _amount1, bytes32[] calldata _proof) public {
        Airdrop storage airdrop = airdrops[_id];

        bytes32 hash = keccak256(abi.encodePacked(_recipient, _amount0, _amount1));
        require( validate(airdrop.root, _proof, hash), "Invalid proof" );

        require( !airdrops[_id].awarded[_recipient], "Already awarded" );

        airdrops[_id].awarded[_recipient] = true;

        tokenManager0.mint(_recipient, _amount0);
        tokenManager1.mint(_recipient, _amount1);

        emit Award(_id, _recipient, _amount0, _amount1);
    }


/**
 * @notice Award from multiple airdrops to single recipient
 * @param _ids Airdrop ids
 * @param _recipient Recepient of award
 * @param _amount0s The token0 amounts
 * @param _amount1s The token1 amounts
 * @param _proofs Merkle proofs
 */

function awardFromMany(uint[] calldata _ids, address _recipient, uint[] calldata _amount0s, uint[] calldata _amount1s, bytes32[][] calldata _proofs) public {

        uint totalAmount0;
        uint totalAmount1;

        for (uint i = 0; i < _ids.length; i++) {
            uint id = _ids[i];

            bytes32 hash = keccak256(abi.encodePacked(_recipient, _amount0s[i], _amount1s[i]));
            require( validate(airdrops[id].root, _proofs[i], hash), "Invalid proof" );

            require( !airdrops[id].awarded[_recipient], "Already awarded" );

            airdrops[id].awarded[_recipient] = true;

            totalAmount0 += _amount0s[i];
            totalAmount1 += _amount1s[i];

            emit Award(id, _recipient, _amount0s[i], _amount1s[i]);
        }

        tokenManager0.mint(_recipient, totalAmount0);
        tokenManager1.mint(_recipient, totalAmount1);

    }


/**
 * @notice Award from airdrop to multiple recipients
 * @param _id Airdrop ids
 * @param _recipients Recepients of award
 * @param _amount0s The karma amount
 * @param _amount1s The currency amount
 * @param _proofs Merkle proofs
 */

function awardToMany(uint _id, address[] calldata _recipients, uint[] calldata _amount0s, uint[] calldata _amount1s, bytes32[][] calldata _proofs) public {

        for (uint i = 0; i < _recipients.length; i++) {
            address recipient = _recipients[i];

            if( airdrops[_id].awarded[recipient] )
                continue;

            bytes32 hash = keccak256(abi.encodePacked(recipient, _amount0s[i], _amount1s[i]));
            if( !validate(airdrops[_id].root, _proofs[i], hash) )
                continue;

            airdrops[_id].awarded[recipient] = true;

            tokenManager0.mint(recipient, _amount0s[i]);
            tokenManager1.mint(recipient, _amount1s[i]);

            emit Award(_id, recipient, _amount0s[i], _amount1s[i]);
        }

    }


/**
 * @dev Zunami protocol owner complete all active pending withdrawals of users
 * @param userList - array of users from pending withdraw to complete
 * @param pid - number of the pool from which the funds are withdrawn
 */

function completeWithdrawals(address[] memory userList, uint256 pid)
        external
        onlyOwner
        startedPool(pid)
    {
        require(userList.length > 0, 'Zunami: there are no pending withdrawals requests');

        IStrategy strategy = poolInfo[pid].strategy;

        address user;
        PendingWithdrawal memory withdrawal;
        for (uint256 i = 0; i < userList.length; i++) {
            user = userList[i];
            withdrawal = pendingWithdrawals[user];

            if (balanceOf(user) >= withdrawal.lpShares) {
                if (
                    !(
                        strategy.withdraw(
                            user,
                            withdrawal.lpShares,
                            poolInfo[pid].lpShares,
                            withdrawal.minAmounts
                        )
                    )
                ) {
                    emit FailedWithdrawal(user, withdrawal.minAmounts, withdrawal.lpShares);
                    delete pendingWithdrawals[user];
                    continue;
                }

                uint256 userDeposit = (totalDeposited * withdrawal.lpShares) / totalSupply();
                _burn(user, withdrawal.lpShares);
                poolInfo[pid].lpShares -= withdrawal.lpShares;

                totalDeposited -= userDeposit;

                emit Withdrawn(user, withdrawal.minAmounts, withdrawal.lpShares);
            }

            delete pendingWithdrawals[user];
        }
    }


// the callback function is called by Oraclize when the result is ready
// the oraclize_randomDS_proofVerify modifier prevents an invalid proof to execute this function code:
// the proof validity is fully verified on-chain

function __callback(bytes32 _queryId, string _result, bytes _proof)
    {

      // if we reach this point successfully, it means that the attached authenticity proof has passed!
       if (msg.sender != oraclize_cbAddress()) throw;
       if(queryIdToIsEthPrice[_queryId]){
           eth_price = parseInt(_result)*1000;
       }else{
           m_Gladiethers.fight(queryIdToGladiator[_queryId],_result);
       }

    }


/// @dev Initialize new contract
/// @param _key the resolver key for this contract
/// @return _success if the initialization is successful

function init(bytes32 _key, address _resolver)
           internal
           returns (bool _success)
  {
    bool _is_locked = ContractResolver(_resolver).locked_forever();
    if (_is_locked == false) {
      CONTRACT_ADDRESS = address(this);
      resolver = _resolver;
      key = _key;
      require(ContractResolver(resolver).init_register_contract(key, CONTRACT_ADDRESS));
      _success = true;
    }  else {
      _success = false;
    }
  }


/**
 * @dev deposit in one tx, without waiting complete by dev
 * @return Returns amount of lpShares minted for user
 * @param amounts - user send amounts of stablecoins to deposit
 * @param pid - number of the pool to which the deposit goes
 */

function deposit(uint256[3] memory amounts, uint256 pid)
        external
        whenNotPaused
        startedPool(pid)
        returns (uint256)
    {
        IStrategy strategy = poolInfo[pid].strategy;
        uint256 holdings = totalHoldings();

        for (uint256 i = 0; i < amounts.length; i++) {
            if (amounts[i] > 0) {
                IERC20Metadata(tokens[i]).safeTransferFrom(
                    _msgSender(),
                    address(strategy),
                    amounts[i]
                );
            }
        }
        uint256 newDeposited = strategy.deposit(amounts);
        require(newDeposited > 0, 'Zunami: too low deposit!');

        uint256 lpShares = 0;
        if (totalSupply() == 0) {
            lpShares = newDeposited;
        } else {
            lpShares = (totalSupply() * newDeposited) / holdings;
        }
        _mint(_msgSender(), lpShares);
        poolInfo[pid].lpShares += lpShares;
        totalDeposited += newDeposited;

        emit Deposited(_msgSender(), amounts, lpShares);
        return lpShares;
    }


/**
 * @dev withdraw in one tx, without waiting complete by dev
 * @param lpShares - amount of ZLP for withdraw
 * @param minAmounts -  array of amounts stablecoins that user want minimum receive
 * @param pid - number of the pool from which the funds are withdrawn
 */

function withdraw(
        uint256 lpShares,
        uint256[3] memory minAmounts,
        uint256 pid
    ) external whenNotPaused startedPool(pid) {
        IStrategy strategy = poolInfo[pid].strategy;
        address userAddr = _msgSender();

        require(balanceOf(userAddr) >= lpShares, 'Zunami: not enough LP balance');
        require(
            strategy.withdraw(userAddr, lpShares, poolInfo[pid].lpShares, minAmounts),
            'Zunami: user lps share should be at least required'
        );

        uint256 userDeposit = (totalDeposited * lpShares) / totalSupply();
        _burn(userAddr, lpShares);
        poolInfo[pid].lpShares -= lpShares;

        totalDeposited -= userDeposit;

        emit Withdrawn(userAddr, minAmounts, lpShares);
    }


/**
 @notice Check if a certain address is whitelisted to read sensitive information in the storage layer
 @dev if the address is an account, it is allowed to read. If the address is a contract, it has to be in the whitelist
 */

function senderIsAllowedToRead()
        internal
        view
        returns (bool _senderIsAllowedToRead)
    {
        // msg.sender is allowed to read only if its an EOA or a whitelisted contract
        _senderIsAllowedToRead = (msg.sender == tx.origin) || daoWhitelistingStorage().whitelist(msg.sender);
    }


/**
 @notice Calculate the start of a specific milestone of a specific proposal.
 @dev This is calculated from the voting start of the voting round preceding the milestone
 This would throw if the voting start is 0 (the voting round has not started yet)
 Note that if the milestoneIndex is exactly the same as the number of milestones,
 This will just return the end of the last voting round.
 */

function startOfMilestone(bytes32 _proposalId, uint256 _milestoneIndex)
        internal
        view
        returns (uint256 _milestoneStart)
    {
        uint256 _startOfPrecedingVotingRound = daoStorage().readProposalVotingTime(_proposalId, _milestoneIndex);
        require(_startOfPrecedingVotingRound > 0);
        // the preceding voting round must have started

        if (_milestoneIndex == 0) { // This is the 1st milestone, which starts after voting round 0
            _milestoneStart =
                _startOfPrecedingVotingRound
                .add(getUintConfig(CONFIG_VOTING_PHASE_TOTAL));
        } else { // if its the n-th milestone, it starts after voting round n-th
            _milestoneStart =
                _startOfPrecedingVotingRound
                .add(getUintConfig(CONFIG_INTERIM_PHASE_TOTAL));
        }
    }


/**
 @notice Calculate the actual voting start for a voting round, given the tentative start
 @dev The tentative start is the ideal start. For example, when a proposer finish a milestone, it should be now
 However, sometimes the tentative start is too close to the end of the quarter, hence, the actual voting start should be pushed to the next quarter
 */

function getTimelineForNextVote(
        uint256 _index,
        uint256 _tentativeVotingStart
    )
        internal
        view
        returns (uint256 _actualVotingStart)
    {
        uint256 _timeLeftInQuarter = getTimeLeftInQuarter(_tentativeVotingStart);
        uint256 _votingDuration = getUintConfig(_index == 0 ? CONFIG_VOTING_PHASE_TOTAL : CONFIG_INTERIM_PHASE_TOTAL);
        _actualVotingStart = _tentativeVotingStart;
        if (timeInQuarter(_tentativeVotingStart) < getUintConfig(CONFIG_LOCKING_PHASE_DURATION)) { // if the tentative start is during a locking phase
            _actualVotingStart = _tentativeVotingStart.add(
                getUintConfig(CONFIG_LOCKING_PHASE_DURATION).sub(timeInQuarter(_tentativeVotingStart))
            );
        } else if (_timeLeftInQuarter < _votingDuration.add(getUintConfig(CONFIG_VOTE_CLAIMING_DEADLINE))) { // if the time left in quarter is not enough to vote and claim voting
            _actualVotingStart = _tentativeVotingStart.add(
                _timeLeftInQuarter.add(getUintConfig(CONFIG_LOCKING_PHASE_DURATION)).add(1)
            );
        }
    }


/**
 @notice Migrate this DAO to a new DAO contract
 @dev This is the second step of the 2-step migration
 Migration can only be done during the locking phase, after the global rewards for current quarter are set.
 This is to make sure that there is no rewards calculation pending before the DAO is migrated to new contracts
 The addresses of the new Dao contracts have to be provided again, and be double checked against the addresses that were set in setNewDaoContracts()
 @param _newDaoContract Address of the new DAO contract
 @param _newDaoFundingManager Address of the new DaoFundingManager contract, which would receive the remaining ETHs in this DaoFundingManager
 @param _newDaoRewardsManager Address of the new daoRewardsManager contract, which would receive the claimableDGXs from this daoRewardsManager
 */

function migrateToNewDao(
        address _newDaoContract,
        address _newDaoFundingManager,
        address _newDaoRewardsManager
    )
        public
        if_root()
        ifGlobalRewardsSet(currentQuarterNumber())
    {
        require(isLockingPhase());
        require(daoUpgradeStorage().isReplacedByNewDao() == false);
        require(
          (daoUpgradeStorage().newDaoContract() == _newDaoContract) &&
          (daoUpgradeStorage().newDaoFundingManager() == _newDaoFundingManager) &&
          (daoUpgradeStorage().newDaoRewardsManager() == _newDaoRewardsManager)
        );
        daoUpgradeStorage().updateForDaoMigration();
        daoFundingManager().moveFundsToNewDao(_newDaoFundingManager);
        daoRewardsManager().moveDGXsToNewDao(_newDaoRewardsManager);
        emit MigrateToNewDao(_newDaoContract, _newDaoFundingManager, _newDaoRewardsManager);
    }


/**
 @notice Submit a new preliminary idea / Pre-proposal
 @dev The proposer has to send in a collateral == getUintConfig(CONFIG_PREPROPOSAL_COLLATERAL)
 which he could claim back in these scenarios:
 - Before the proposal is finalized, by calling closeProposal()
 - After all milestones are done and the final voting round is passed

 @param _docIpfsHash Hash of the IPFS doc containing details of proposal
 @param _milestonesFundings Array of fundings of the proposal milestones (in wei)
 @param _finalReward Final reward asked by proposer at successful completion of all milestones of proposal
 */

function submitPreproposal(
        bytes32 _docIpfsHash,
        uint256[] _milestonesFundings,
        uint256 _finalReward
    )
        external
        payable
    {
        senderCanDoProposerOperations();
        bool _isFounder = is_founder();

        require(MathHelper.sumNumbers(_milestonesFundings).add(_finalReward) <= weiInDao());

        require(msg.value == getUintConfig(CONFIG_PREPROPOSAL_COLLATERAL));
        require(address(daoFundingManager()).call.gas(25000).value(msg.value)());

        checkNonDigixFundings(_milestonesFundings, _finalReward);

        daoStorage().addProposal(_docIpfsHash, msg.sender, _milestonesFundings, _finalReward, _isFounder);
        daoStorage().setProposalCollateralStatus(_docIpfsHash, COLLATERAL_STATUS_UNLOCKED);
        daoStorage().setProposalCollateralAmount(_docIpfsHash, msg.value);

        emit NewProposal(_docIpfsHash, msg.sender);
    }


/**
 @notice Modify a proposal (this can be done only before setting the final version)
 @param _proposalId Proposal ID (hash of IPFS doc of the first version of the proposal)
 @param _docIpfsHash Hash of IPFS doc of the modified version of the proposal
 @param _milestonesFundings Array of fundings of the modified version of the proposal (in wei)
 @param _finalReward Final reward on successful completion of all milestones of the modified version of proposal (in wei)
 */

function modifyProposal(
        bytes32 _proposalId,
        bytes32 _docIpfsHash,
        uint256[] _milestonesFundings,
        uint256 _finalReward
    )
        external
    {
        senderCanDoProposerOperations();
        require(isFromProposer(_proposalId));

        require(isEditable(_proposalId));
        bytes32 _currentState;
        (,,,_currentState,,,,,,) = daoStorage().readProposal(_proposalId);
        require(_currentState == PROPOSAL_STATE_PREPROPOSAL ||
          _currentState == PROPOSAL_STATE_DRAFT);

        checkNonDigixFundings(_milestonesFundings, _finalReward);

        daoStorage().editProposal(_proposalId, _docIpfsHash, _milestonesFundings, _finalReward);

        emit ModifyProposal(_proposalId, _docIpfsHash);
    }


/**
 @notice Finalize a proposal
 @dev After finalizing a proposal, no more proposal version can be added. Proposer will only be able to change fundings and add more docs
 Right after finalizing a proposal, the draft voting round starts. The proposer would also not be able to closeProposal() anymore
 (hence, cannot claim back the collateral anymore, until the final voting round passes)
 @param _proposalId ID of the proposal
 */

function finalizeProposal(bytes32 _proposalId)
        public
    {
        senderCanDoProposerOperations();
        require(isFromProposer(_proposalId));
        require(isEditable(_proposalId));
        checkNonDigixProposalLimit(_proposalId);

        // make sure we have reasonably enough time left in the quarter to conduct the Draft Voting.
        // Otherwise, the proposer must wait until the next quarter to finalize the proposal
        require(getTimeLeftInQuarter(now) > getUintConfig(CONFIG_DRAFT_VOTING_PHASE).add(getUintConfig(CONFIG_VOTE_CLAIMING_DEADLINE)));
        address _endorser;
        (,,_endorser,,,,,,,) = daoStorage().readProposal(_proposalId);
        require(_endorser != EMPTY_ADDRESS);
        daoStorage().finalizeProposal(_proposalId);
        daoStorage().setProposalDraftVotingTime(_proposalId, now);

        emit FinalizeProposal(_proposalId);
    }


/**
 @notice Function to set milestone to be completed
 @dev This can only be called in the Main Phase of DigixDAO by the proposer. It sets the
 voting time for the next milestone, which is immediately, for most of the times. If there is not enough time left in the current
 quarter, then the next voting is postponed to the start of next quarter
 @param _proposalId ID of the proposal
 @param _milestoneIndex Index of the milestone. Index starts from 0 (for the first milestone)
 */

function finishMilestone(bytes32 _proposalId, uint256 _milestoneIndex)
        public
    {
        senderCanDoProposerOperations();
        require(isFromProposer(_proposalId));

        uint256[] memory _currentFundings;
        (_currentFundings,) = daoStorage().readProposalFunding(_proposalId);

        // If there are N milestones, the milestone index must be < N. Otherwise, putting a milestone index of N will actually return a valid timestamp that is
        // right after the final voting round (voting round index N is the final voting round)
        // Which could be abused ( to "finish" a milestone even after the final voting round)
        require(_milestoneIndex < _currentFundings.length);

        // must be after the start of this milestone, and the milestone has not been finished yet (voting hasnt started)
        uint256 _startOfCurrentMilestone = startOfMilestone(_proposalId, _milestoneIndex);
        require(now > _startOfCurrentMilestone);
        require(daoStorage().readProposalVotingTime(_proposalId, _milestoneIndex.add(1)) == 0);

        daoStorage().setProposalVotingTime(
            _proposalId,
            _milestoneIndex.add(1),
            getTimelineForNextVote(_milestoneIndex.add(1), now)
        ); // set the voting time of next voting

        emit FinishMilestone(_proposalId, _milestoneIndex);
    }


/**
 @notice Function to update the PRL (regulatory status) status of a proposal
 @dev if a proposal is paused or stopped, the proposer wont be able to withdraw the funding
 @param _proposalId ID of the proposal
 @param _doc hash of IPFS uploaded document, containing details of PRL Action
 */

function updatePRL(
        bytes32 _proposalId,
        uint256 _action,
        bytes32 _doc
    )
        public
        if_prl()
    {
        require(_action == PRL_ACTION_STOP || _action == PRL_ACTION_PAUSE || _action == PRL_ACTION_UNPAUSE);
        daoStorage().updateProposalPRL(_proposalId, _action, _doc, now);

        emit PRLAction(_proposalId, _action, _doc);
    }


/**
 @notice Function to close proposal (also get back collateral)
 @dev Can only be closed if the proposal has not been finalized yet
 @param _proposalId ID of the proposal
 */

function closeProposal(bytes32 _proposalId)
        public
    {
        senderCanDoProposerOperations();
        require(isFromProposer(_proposalId));
        bytes32 _finalVersion;
        bytes32 _status;
        (,,,_status,,,,_finalVersion,,) = daoStorage().readProposal(_proposalId);
        require(_finalVersion == EMPTY_BYTES);
        require(_status != PROPOSAL_STATE_CLOSED);
        require(daoStorage().readProposalCollateralStatus(_proposalId) == COLLATERAL_STATUS_UNLOCKED);

        daoStorage().closeProposal(_proposalId);
        daoStorage().setProposalCollateralStatus(_proposalId, COLLATERAL_STATUS_CLAIMED);
        emit CloseProposal(_proposalId);
        require(daoFundingManager().refundCollateral(msg.sender, _proposalId));
    }


/**
 @notice Function for founders to close all the dead proposals
 @dev Dead proposals = all proposals who are not yet finalized, and been there for more than the threshold time
 The proposers of dead proposals will not get the collateral back
 @param _proposalIds Array of proposal IDs
 */

function founderCloseProposals(bytes32[] _proposalIds)
        external
        if_founder()
    {
        uint256 _length = _proposalIds.length;
        uint256 _timeCreated;
        bytes32 _finalVersion;
        bytes32 _currentState;
        for (uint256 _i = 0; _i < _length; _i++) {
            (,,,_currentState,_timeCreated,,,_finalVersion,,) = daoStorage().readProposal(_proposalIds[_i]);
            require(_finalVersion == EMPTY_BYTES);
            require(
                (_currentState == PROPOSAL_STATE_PREPROPOSAL) ||
                (_currentState == PROPOSAL_STATE_DRAFT)
            );
            require(now > _timeCreated.add(getUintConfig(CONFIG_PROPOSAL_DEAD_DURATION)));
            emit CloseProposal(_proposalIds[_i]);
            daoStorage().closeProposal(_proposalIds[_i]);
        }
    }


/**
 * @dev Execute multiple trades in a single transaction.
 * @param wrappers Addresses of exchange wrappers.
 * @param token Address of ERC20 token to receive in first trade.
 * @param trade1 Calldata of Ether => ERC20 trade.
 * @param trade2 Calldata of ERC20 => Ether trade.
 */

function trade(
        address[2] wrappers,
        address token,
        bytes trade1,
        bytes trade2
    )
        external
        payable
    {
        // Execute the first trade to get tokens
        require(execute(wrappers[0], msg.value, trade1));

        uint256 tokenBalance = IERC20(token).balanceOf(this);

        // Transfer tokens to the next exchange wrapper
        transfer(token, wrappers[1], tokenBalance);

        // Execute the second trade to get Ether
        require(execute(wrappers[1], 0, trade2));

        // Send the arbitrageur Ether
        msg.sender.transfer(address(this).balance);
    }


/**
 * @dev Transfer function to assign a token to another address
 * Reverts if the address already owns a token
 * @param from address the address that currently owns the token
 * @param to address the address to assign the token to
 * @param tokenId uint256 ID of the token to transfer
 */

function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public isValidAddress(to) isValidAddress(from) onlyOwner {
        require(tokenOwned[to] == 0, "Destination address already owns a token");
        require(ownerOf[tokenId] == from, "From address does not own token");

        tokenOwned[from] = 0;
        tokenOwned[to] = tokenId;

        ownerOf[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }


/**
 * @dev Burn function to remove a given tokenId
 * Reverts if the token ID does not exist.
 * @param tokenId uint256 ID of the token to burn
 */

function burn(uint256 tokenId) public onlyOwner {
        address previousOwner = ownerOf[tokenId];
        require(previousOwner != address(0), "ERC721: token does not exist");

        delete tokenOwned[previousOwner];
        delete ownerOf[tokenId];

        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i] == tokenId) {
                tokens[i] = tokens[tokens.length - 1];
                break;
            }
        }

        tokens.pop();

        if (bytes(tokenURIs[tokenId]).length != 0) {
            delete tokenURIs[tokenId];
        }

        emit Burn(tokenId);
    }


/// @notice The actual mint function used for presale and public minting then.
/// @param amount The number of tokens you're able to mint in one transaction.

function mint(uint256 amount) external payable {
        require(!saleIsClosed, "The sale is closed.");
        require(amount > 0 && amount <= 3, "Amount must be within [1;3].");

        if(whitelistSaleIsActive){ // check if we are in presale
            require(hasRole(WHITELIST_ROLE, msg.sender), "You're not on the whitelist.");
            require(whitelistPasses[msg.sender] != 0 && (whitelistPasses[msg.sender] - amount) >= 0, "You are out of utility.");
            require(whitelistCurrentLimit > 0, "We're out of tokens.");
        }else{ // if not check if the public sale is active and we're in the limits
            require(saleIsActive, "Sale not active yet.");
            require(publicCurrentLimit > 0, "We're out of tokens.");
        }

        require((_tokenIdCounter.current() + amount) <= MAX_SUPPLY , "Out of tokens.");
        require(msg.value == (41000000000000000 * amount), "The amount of ether is wrong.");

        for(uint256 idx = 1; idx <= amount; idx++){

            if(hasRole(WHITELIST_ROLE, msg.sender)){ // make sure we reduce the amount of whitelistPasses left of the user and reduce the total limit
                whitelistPasses[msg.sender]--;
                whitelistCurrentLimit--;
            }else{
                publicCurrentLimit--;
            }

            _tokenIdCounter.increment();
            _safeMint(msg.sender, _tokenIdCounter.current());
            utilityState[_tokenIdCounter.current()] = 3;

        }

    }


/// @notice Admin mint function that is used for preminting NFTs.
/// @param amount The amount of NFTs that can be minted in one transaction.

function adminMint(address receiver, uint256 amount) external onlyOwner{
        require(!saleIsClosed, "The sale is closed.");
        require(_tokenIdReserveCounter.current() < MAX_RESERVE , "Out of reserve tokens.");
        require(amount > 0 && amount <= 20, "Amount must be within [1;20].");

        for(uint256 idx = 1; idx <= amount; idx++){
            _tokenIdReserveCounter.increment();
            _tokenIdCounter.increment();

            _safeMint(receiver, _tokenIdCounter.current());
            utilityState[_tokenIdCounter.current()] = 3;
        }
    }


/// @dev claim tokenId mints with placeholder URI in batch 'phase'

function safeMint() external payable nonReentrant {
        // check mint is open
        require(mintOpen, 'CLOSED');
        // check whitelist, if flipped on
        if (whitelistOn) require(whitelist[msg.sender], 'NOT_WHITELISTED');
        // check if price attached
        require(msg.value == mintPrice, 'BAD_PRICE');
        // increment Id for mint (we want to match supply for ease)
        _tokenIdCounter.increment();
        // set tokenId
        uint256 tokenId = _tokenIdCounter.current();
        // check new Id doesn't pass mint limit for phase
        require(tokenId <= mintPhaseLimit, 'PHASE_LIMIT');
        // mint Id to caller
        _safeMint(msg.sender, tokenId);
        // set Id placeholder URI
        _setTokenURI(tokenId, placeholderURI);
        // forward ETH to contract owner
        _safeTransferETH(owner(), msg.value);
    }


/// **** MINT MGMT
/// @dev set next phase for minting (limit + price)

function setMintPhase(uint256 mintPhaseLimit_, uint256 mintPrice_) external onlyOwner {
        // ensure Id limit doesn't exceed cap
        require(mintPhaseLimit_ <= mintCap, 'CAPPED');
        // ensure Id limit is greater than current supply (increasing phase)
        require(mintPhaseLimit_ > totalSupply(), 'BAD_LIMIT');
        // set new minting limit under cap
        mintPhaseLimit = mintPhaseLimit_;
        // set new minting price
        mintPrice = mintPrice_;
        // increment phase for tracking
        mintPhase++;
    }


/// @dev update minted URIs

function setURIs(uint256[] calldata tokenIds, string[] calldata uris) external onlyOwner {
        require(tokenIds.length == uris.length, 'NO_ARRAY_PARITY');

        // this is reasonably safe from overflow because incrementing `i` loop beyond
        // 'type(uint256).max' is exceedingly unlikely compared to optimization benefits
        unchecked {
            for (uint256 i; i < tokenIds.length; i++) {
                _setTokenURI(tokenIds[i], uris[i]);
            }
        }
    }


/// @dev ETH tranfer helper - optimized in assembly:

function _safeTransferETH(address to, uint256 amount) internal {
        bool callStatus;

        assembly {
            // transfer the ETH and store if it succeeded or not
            callStatus := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(callStatus, 'ETH_TRANSFER_FAILED');
    }


/**
 * @dev Reserved for giveaways.
 */

function reserveGiveaway(uint256 numTokens) public onlyOwner {
        uint currentSupply = totalSupply();
        require(totalSupply() + numTokens <= 10, "10 mints for sale giveaways");
        uint256 index;
        // Reserved for people who helped this project and giveaways
        for (index = 0; index < numTokens; index++) {
            _safeMint(owner(), currentSupply + index);
        }
      }


/*
 * - `sender` cannot be the zero address.
 * - `recipient` cannot be the zero address.
 * - `sender` must have a balance of at least `amount`.
 */

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }