//always removing in renBTC, else use normal method

function removeLiquidityOneCoinThenBurn(bytes calldata _btcDestination, uint256 _token_amounts, uint256 min_amount) external {
        uint256 startRenbtcBalance = RENBTC.balanceOf(address(this));
        require(curveToken.transferFrom(msg.sender, address(this), _token_amounts));
        exchange.remove_liquidity_one_coin(_token_amounts, 0, min_amount);
        uint256 endRenbtcBalance = RENBTC.balanceOf(address(this));
        uint256 renbtcWithdrawn = endRenbtcBalance.sub(startRenbtcBalance);

        // Burn and send proceeds to the User
        uint256 burnAmount = registry.getGatewayBySymbol("BTC").burn(_btcDestination, renbtcWithdrawn);
        emit Burn(burnAmount);
    }


/**
 * @notice Start airdrop
 * @param _multiplierPercent Multiplier of the airdrop
 */

function startAirdrop(uint256 _multiplierPercent) onlyOwner external returns(bool){
        pause();
        require(multiplierPercent == 0);                 //This means airdrop was finished
        require(_multiplierPercent > PERCENT_DIVIDER);   //Require that after airdrop amount of tokens will be greater than before
        currentAirdrop = currentAirdrop.add(1);
        multiplierPercent = _multiplierPercent;
        undropped = totalSupply();
        assert(multiplierPercent.mul(undropped) > 0);   //Assert that wrong multiplier will not result in owerflow in airdropAmount()
        AirdropStart(multiplierPercent, currentAirdrop);
    }


/**
 * @notice Execute airdrop for a bunch of addresses. Should be repeated for all addresses with non-zero amount of tokens.
 * @dev This function can be called by anyone, not only the owner
 * @param holders Array of token holder addresses.
 * @return true if success
 */

function drop(address[] holders) external returns(bool){
        for(uint256 i=0; i < holders.length; i++){
            address holder = holders[i];
            if(!isAirdropped(holder)){
                uint256 balance = balances[holder];
                undropped = undropped.sub(balance);
                balances[holder] = airdropAmount(balance);
                uint256 amount = balances[holder].sub(balance);
                totalSupply_ = totalSupply_.add(amount);
                Transfer(address(0), holder, amount);
                setAirdropped(holder);
            }
        }
    }


/**
 * @param _value the amount of money to burn
 */

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


/**
 * @param _from the address of the sender
 * @param _value the amount of money to burn
 */

function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
        require(_value <= allowance[_from][msg.sender]);    // Check allowance
        balanceOf[_from] -= _value;                         // Subtract from the targeted balance
        allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
        totalSupply -= _value;                              // Update totalSupply
        emit Burn(_from, _value);
        return true;
    }


// function testrevenue(address _game) public view returns (uint256, bool, bool, uint256){
//     uint256 ethfee;
//     uint256 hbfee;
//     address local_game = _game;
//     IERC721 erc721Address = IERC721(_game);
//     for (uint i = 0; i < Games[_game].tokenIdSale.length; i++) {
//         uint256 _tokenId = Games[_game].tokenIdSale[i];
//         if (Games[local_game].tokenPrice[_tokenId].tokenOwner == erc721Address.ownerOf(_tokenId)) {
//             if (Games[local_game].tokenPrice[_tokenId].fee >= 0) {
//                 ethfee = ethfee.add(Games[local_game].tokenPrice[_tokenId].fee);
//                 if(Games[local_game].tokenPrice[_tokenId].isHightlight == 1) ethfee = ethfee.add(Games[local_game].hightLightFee);
//             }
//             else if (Games[local_game].tokenPrice[_tokenId].hbfee >= 0) {
//                 hbfee = hbfee.add(Games[local_game].tokenPrice[_tokenId].hbfee);
//                 if(Games[local_game].tokenPrice[_tokenId].isHightlight == 1) hbfee = hbfee.add(Games[local_game].hightLightFee.mul(HBWALLETExchange).div(2).div(10 ** 16));
//             }
//         }
//         if(i== Games[local_game].tokenIdSale.length-1) {
//             uint256 eth = address(this).balance;
//             uint256 hb = hbwalletToken.balanceOf(address(this));
//             return (ethfee, Games[local_game].tokenPrice[_tokenId].tokenOwner == erc721Address.ownerOf(_tokenId),
//             Games[local_game].tokenPrice[_tokenId].fee >= 0, ethfee.add(Games[local_game].hightLightFee));
//         }
//     }
// }

function revenue() public view returns (uint256, uint){

        uint256 ethfee;
        uint256 hbfee;
        for(uint j = 0; j< arrGames.length; j++) {

            address _game = arrGames[j];
            IERC721 erc721Address = IERC721(arrGames[j]);
            for (uint i = 0; i < Games[_game].tokenIdSale.length; i++) {
                uint256 _tokenId = Games[_game].tokenIdSale[i];
                if (Games[_game].tokenPrice[_tokenId].tokenOwner == erc721Address.ownerOf(_tokenId)) {
                    if (Games[_game].tokenPrice[_tokenId].fee >= 0) {
                        ethfee = ethfee.add(Games[_game].tokenPrice[_tokenId].fee);
                        if(Games[_game].tokenPrice[_tokenId].isHightlight == 1) ethfee = ethfee.add(Games[_game].hightLightFee);
                    }
                    else if (Games[_game].tokenPrice[_tokenId].hbfee >= 0) {
                        hbfee = hbfee.add(Games[_game].tokenPrice[_tokenId].hbfee);
                        if(Games[_game].tokenPrice[_tokenId].isHightlight == 1) hbfee = hbfee.add(Games[_game].hightLightFee.mul(HBWALLETExchange).div(2).div(10 ** 16));
                    }
                }
            }
        }

        uint256 eth = address(this).balance.sub(ethfee);
        uint256 hb = hbwalletToken.balanceOf(address(this)).sub(hbfee);
        return (eth, hb);
    }


/// @notice Enables calling multiple methods in a single call to this contract.

function multicall(bytes[] calldata data) external returns (bytes[] memory results) {
        results = new bytes[](data.length);
        unchecked {for (uint256 i = 0; i < data.length; i++) {
            (bool success, bytes memory result) = address(this).delegatecall(data[i]);
            if (!success) {
                if (result.length < 68) revert();
                assembly {result := add(result, 0x04)}
                revert(abi.decode(result, (string)));
            }
            results[i] = result;}}
    }


/// @notice Enter Meowshi. Deposit xSUSHI `amount`. Mint MEOW for `to`.

function meow(address to, uint256 amount) external returns (uint256 shares) {
        ISushiBar(sushiBar).transferFrom(msg.sender, address(bento), amount); // forward to BENTO for skim
        (, shares) = bento.deposit(IERC20(sushiBar), address(bento), address(this), amount, 0);
        meowMint(to, shares * multiplier);
    }


// Sets the price for the NFT's

function getHeroPrice() public view returns (uint256) {
    uint currentSupply = totalSupply();
    if (currentSupply >= 0 && currentSupply < 501){
        return 35000000000000000;
    }else if(currentSupply >= 501 && currentSupply < 5000){
        return 65000000000000000;
    }else if(currentSupply >= 5001){
        return 70000000000000000;
    }
   }


// minting function for stakes

function mintForStakers(uint256 numNFTs) public payable onlyRole(STAKER_ROLE){
    require(hasSaleStarted, "Sale has not started");
    require(stakerSupply > 0, "Staker reserve is empty!");
    require(msg.value >= getHeroPrice() * numNFTs, "Incorrect ether value");
    require(stakerSupply - numNFTs >= 0, "Exceeds retail supply");
    require(numNFTs <= priceRange(), "Number of NFTs would exceed the price range");

    for (uint i = 0; i < numNFTs; i++) {
      uint mintIndex = totalSupply() + 1; // +1 so it doesn't start on index 0.
      _safeMint(msg.sender, mintIndex);
    }

    if (startingIndexBlock == 0 && (totalSupply() == retailSupply)) {
      startingIndexBlock = block.number;}

    stakerSupply = stakerSupply - numNFTs;
    stakerMinted = stakerMinted + numNFTs;
  }


/**
 * @dev Returns the value for `variable` at the indexed sample.
 */

function getInstantValue(
        mapping(uint256 => bytes32) storage samples,
        IPriceOracle.Variable variable,
        uint256 index
    ) external view returns (uint256) {
        bytes32 sample = samples[index];
        _require(sample.timestamp() > 0, Errors.ORACLE_NOT_INITIALIZED);

        int256 rawInstantValue = sample.instant(variable);
        return LogCompression.fromLowResLog(rawInstantValue);
    }


/**
 * @dev Returns the time average weighted price corresponding to `query`.
 */

function getTimeWeightedAverage(
        mapping(uint256 => bytes32) storage samples,
        IPriceOracle.OracleAverageQuery memory query,
        uint256 latestIndex
    ) external view returns (uint256) {
        _require(query.secs != 0, Errors.ORACLE_BAD_SECS);

        int256 beginAccumulator = getPastAccumulator(samples, query.variable, latestIndex, query.ago + query.secs);
        int256 endAccumulator = getPastAccumulator(samples, query.variable, latestIndex, query.ago);
        return LogCompression.fromLowResLog((endAccumulator - beginAccumulator) / int256(query.secs));
    }


///@notice Add item to progject vote list
/// @dev It must be call from owner before startVote()
/// @param _prjName   - string, project name for vote.
/// @param _prjAddress   - address, only this address can get
/// reward if project will win.

function addProjectToVote(string calldata _prjName, address _prjAddress)
    external
    payable
    onlyOwner
    {
        require(currentStage == StageName.preList, "Can't add item after vote has starting!");
        require(_prjAddress != address(0),"Address must be valid!");
        bytes32 hash = keccak256(bytes(_prjName));
        require( projects[hash].prjAddress == address(0),
            "It seems like this item allready exist!"
        );
        projects[hash] = PrjProperties({
                prjAddress: _prjAddress,
                voteCount: 0,
                prjWeiRaised: 0
            });
    }


///@notice Make vote for sender
/// @dev Sender must send enough ether
/// @param _prjName   - string, project name for vote.

function vote(string calldata _prjName) external payable {
        require(currentStage == StageName.inProgress,
            "Vote disable now!"

        );
        require(msg.value >= MINETHVOTE, "Please send more ether!");
        bytes32 hash = keccak256(bytes(_prjName));
        PrjProperties memory currentBet = projects[hash];//Storage - or   other place!!!!
        require(currentBet.prjAddress != address(0),
            "It seems like there is no item with that name"
        );
        projects[hash].voteCount = currentBet.voteCount + 1;
        projects[hash].prjWeiRaised = currentBet.prjWeiRaised + msg.value;
        emit NewBet(msg.sender, msg.value, _prjName);
        //Check for new winner
        if  (currentBet.voteCount + 1 > projects[keccak256(bytes(currentWinner))].voteCount)
            currentWinner = _prjName;
        //Check vote end
        if  (now >= voteFinishDate)
            currentStage = StageName.voteFinished;
            emit VoteFinished(msg.sender, uint64(now));

    }


// Mint weebTEND with TEND

function mint(uint256 amount) public {
        require(amount <= TEND.balanceOf(msg.sender), ">TEND.balanceOf");
        uint256 totalStakedTendiesBefore = getTotalStakedTendies();

        // Stake tendies
        TEND.safeTransferFrom(msg.sender, address(this), amount);

        uint256 totalStakedTendiesAfter = getTotalStakedTendies();

        if (totalSupply() == 0) {
            return super._mint(msg.sender, amount.mul(INITIAL_SUPPLY_MULTIPLIER));
        }

        uint256 mintAmount = (totalStakedTendiesAfter.sub(totalStakedTendiesBefore))
            .mul(totalSupply())
            .div(totalStakedTendiesBefore);

        emit Mint(msg.sender, mintAmount, block.timestamp);
        return super._mint(msg.sender, mintAmount);
    }


// Burn weebTEND to get collected TEND

function burn(uint256 amount) public nonReentrant {
        require(amount <= balanceOf(msg.sender), ">weebTEND.balanceOf");

        // Burn weebTEND
        uint256 proRataTend = getTotalStakedTendies().mul(amount).div(totalSupply());
        super._burn(msg.sender, amount);
        emit Burn(msg.sender, amount, block.timestamp);

        // Calculate burn fee and transfer underlying TEND
        uint256 _fee = proRataTend.mul(_burnFeeRate).div(_burnFeeBase);
        TEND.safeTransfer(msg.sender, proRataTend.sub(_fee));
        feesCollected += _fee;
    }


// Utility function to verify geth style signatures

function verifySig(
		address _signer,
		bytes32 _theHash,
		uint8 _v,
		bytes32 _r,
		bytes32 _s
	) private pure returns (bool) {
		bytes32 messageDigest =
			keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _theHash));
		return _signer == ecrecover(messageDigest, _v, _r, _s);
	}


// Make a new checkpoint from the supplied validator set
// A checkpoint is a hash of all relevant information about the valset. This is stored by the contract,
// instead of storing the information directly. This saves on storage and gas.
// The format of the checkpoint is:
// h(gravityId, "checkpoint", valsetNonce, validators[], powers[])
// Where h is the keccak256 hash function.
// The validator powers must be decreasing or equal. This is important for checking the signatures on the
// next valset, since it allows the caller to stop verifying signatures once a quorum of signatures have been verified.

function makeCheckpoint(
		address[] memory _validators,
		uint256[] memory _powers,
		uint256 _valsetNonce,
		bytes32 _gravityId
	) private pure returns (bytes32) {
		// bytes32 encoding of the string "checkpoint"
		bytes32 methodName = 0x636865636b706f696e7400000000000000000000000000000000000000000000;

		bytes32 checkpoint =
			keccak256(abi.encode(_gravityId, methodName, _valsetNonce, _validators, _powers));

		return checkpoint;
	}


// Set root of merkle tree

function setRoot(bytes32 _root) public onlyOwner {
        require(address(iEscrowThales) != address(0), "Set Escrow Thales address");
        root = _root;
        startTime = block.timestamp; //reset time every period
        emit NewRoot(_root, block.timestamp, period);
        period = period + 1;
    }


// Check if a given reward has already been claimed

function claimed(uint256 index) public view returns (uint256 claimedBlock, uint256 claimedMask) {
        claimedBlock = _claimed[period][index / 256];
        claimedMask = (uint256(1) << uint256(index % 256));
        require((claimedBlock & claimedMask) == 0, "Tokens have already been claimed");
    }


// Get airdrop tokens assigned to address
// Requires sending merkle proof to the function

function claim(
        uint256 index,
        uint256 amount,
        bytes32[] memory merkleProof
    ) public notPaused {
        // Make sure the tokens have not already been redeemed
        (uint256 claimedBlock, uint256 claimedMask) = claimed(index);
        _claimed[period][index / 256] = claimedBlock | claimedMask;

        // Compute the merkle leaf from index, recipient and amount
        bytes32 leaf = keccak256(abi.encodePacked(index, msg.sender, amount));
        // verify the proof is valid
        require(MerkleProof.verify(merkleProof, root, leaf), "Proof is not valid");

        // Send to EscrowThales contract
        iEscrowThales.addToEscrow(msg.sender, amount);

        emit Claim(msg.sender, amount, block.timestamp);
    }


/**
 * @notice remove an address from the array
 * @dev finds the element, swaps it with the last element, and then deletes it;
 *      returns a boolean whether the element was found and deleted
 * @param self Storage array containing address type variables
 * @param element the element to remove from the array
 */

function removeAddress(Addresses storage self, address element) internal returns (bool) {
        for (uint i = 0; i < self.size(); i++) {
            if (self._items[i] == element) {
                self._items[i] = self._items[self.size() - 1];
                self._items.pop();
                return true;
            }
        }
        return false;
    }


/**
 * @notice check if an element exist in the array
 * @param self Storage array containing address type variables
 * @param element the element to check if it exists in the array
 */

function exists(Addresses storage self, address element) internal view returns (bool) {
        for (uint i = 0; i < self.size(); i++) {
            if (self._items[i] == element) {
                return true;
            }
        }
        return false;
    }


/*function deployMinersTest(uint32 _hour, address user, uint32[] area, uint32[] period, uint32[] count) public checkWhiteList payable {
 require(area.length > 0);
 require(area.length == period.length);
 require(area.length == count.length);
 address _user = user;
 if (_user == address(0)) {
 _user = msg.sender;
 }

 checkArea(area, user);

 uint payment = _deployMiners(_user, _hour, area, period, count);
 _updateCheckPoints(_user, _hour, area, period, count);

 require(payment <= msg.value);
 remainEther[msg.sender] += (msg.value - payment);
 amountEther += payment;
 }*/

function _deployMiners(address _user, uint32 _hour, uint32[] memory area, uint32[] memory period, uint32[] memory count) internal returns(uint){
        uint payment = 0;
        uint32 minerCount = 0;
        uint32[3][72] storage _areaDeployed = areaHourDeployed[_hour];
        uint32[3][72] storage _userAreaDeployed = userAreaHourDeployed[_user].hour[_hour];

        for (uint index = 0; index < area.length; ++index) {
            require (period[index] == 4 || period[index] == 8 || period[index] == 24);
            if (period[index] == 4) {
                _areaDeployed[area[index]][0] += count[index];
                _userAreaDeployed[area[index]][0] += count[index];
                payment += count[index] * MINER_4_HOURS;
            } else if (period[index] == 8) {
                _areaDeployed[area[index]][1] += count[index];
                _userAreaDeployed[area[index]][1] += count[index];
                payment += count[index] * MINER_8_HOURS;
            } else if (period[index] == 24) {
                _areaDeployed[area[index]][2] += count[index];
                _userAreaDeployed[area[index]][2] += count[index];
                payment += count[index] * MINER_24_HOURS;
            }
            minerCount += count[index];
            DeployMiner(_user, area[index], _hour, _hour + period[index], count[index]);

            adjustDeployRange(area[index], _hour, _hour + period[index]);
        }
        return payment;
    }


/**
 * @dev Override to extend the way in which ether is converted to tokens.
 * @param _weiAmount Value in wei to be converted into tokens
 * @return Number of tokens that can be purchased with the specified _weiAmount
 */


// SO - 50375

function testStr(string memory str) internal pure returns (bool){
        bytes memory b = bytes(str);
        if(b.length > 20|| b.length < 1) return false;

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

            if(
                !(ch >= 0x30 && ch <= 0x39) && //9-0
                !(ch >= 0x41 && ch <= 0x5A) && //A-Z
                !(ch >= 0x61 && ch <= 0x7A) && //a-z
                !(ch == 0x20) // space
            )
                return false;
        }

        return true;
    }


// SO - 58341

function toAsciiString(address x) internal pure returns (string memory) {
        bytes memory s = new bytes(40);
        for (uint i = 0; i < 20; i++) {
            bytes1 b = bytes1(uint8(uint(uint160(x)) / (2**(8*(19 - i)))));
            bytes1 hi = bytes1(uint8(b) / 16);
            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
            s[2*i] = char(hi);
            s[2*i+1] = char(lo);
        }
        return string(s);
    }


/**
 * @dev doubles pruf balance at snapshotID(1)
 */

function splitMyPruf() external whenNotPaused {
        require(
            hasSplit[msg.sender] == 0,
            "SPLIT:SMP: Caller address has already been split"
        );
        //^^^^^^^checks^^^^^^^^^

        uint256 balanceAtSnapshot = UTIL_TKN.balanceOfAt(msg.sender, 1);
        hasSplit[msg.sender] = 170; //mark caller address as having been split
        //^^^^^^^effects^^^^^^^^^

        UTIL_TKN.mint(msg.sender, balanceAtSnapshot); //mint the new tokens to caller address
        //^^^^^^^Interactions^^^^^^^^^
    }


/**
 * @dev doubles pruf balance at snapshotID(1)
 * @param _address - address to be split
 */

function splitPrufAtAddress(address _address) external whenNotPaused {
        require(
            hasSplit[_address] == 0,
            "SPLIT:SMPAA: Caller address has already been split"
        );
        //^^^^^^^checks^^^^^^^^^

        uint256 balanceAtSnapshot = UTIL_TKN.balanceOfAt(_address, 1);
        hasSplit[_address] = 170; //mark caller address as having been split
        //^^^^^^^effects^^^^^^^^^

        UTIL_TKN.mint(_address, balanceAtSnapshot); //mint the new tokens to caller address
        //^^^^^^^Interactions^^^^^^^^^
    }


// note this will always return 0 before update has been called successfully for the first time.

function consult(address token, uint amountIn) internal view returns (uint amountOut) {
        if (token == token0) {
            amountOut = price0Average.mul(amountIn).decode144();
        } else {
            require(token == token1, 'ExampleOracleSimple: INVALID_TOKEN');
            amountOut = price1Average.mul(amountIn).decode144();
        }
    }


/**
 * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
 * given `owner`'s signed approval.
 *
 * Emits an {Approval} event.
 *
 * Requirements:
 *
 * - `spender` cannot be the zero address.
 * - `deadline` must be a timestamp in the future.
 * - `v`, `r` and `s` must be a valid signature from `owner`
 * over the EIP712-formatted function arguments.
 * - the signature must use ``owner``'s current nonce (see {nonces}).
 *
 * For more information on the signature format, see the
 * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
 * section].
 */

function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external override onlyOnDeployedNetwork whenNotPaused {
        // solhint-disable-next-line
        require(deadline >= block.timestamp, "ERC20WithPermit: EXPIRED");

        bytes32 digest =
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR,
                    keccak256(
                        abi.encode(
                            PERMIT_TYPEHASH,
                            owner,
                            spender,
                            value,
                            nonces[owner]++,
                            deadline
                        )
                    )
                )
            );

        address recoveredAddress = ecrecover(digest, v, r, s);
        require(
            recoveredAddress != address(0) && recoveredAddress == owner,
            "ERC20WithPermit: INVALID_SIGNATURE"
        );

        _approve(owner, spender, value);
    }


// ------------------------------------------------------------------------
// Accepts ETH and transfers ZAN tokens based on exchage rate and state
// ------------------------------------------------------------------------

function () public payable {
        uint eth_sent = msg.value;
        uint tokens_amount = eth_sent.mul(exchangeRate);

        require(eth_sent > 0);
        require(exchangeRate > 0);
        require(stateStartDate < now && now < stateEndDate);
        require(balances[owner] >= tokens_amount);
        require(_totalSupply - (balances[owner] - tokens_amount) <= saleCap);

        // Don't accept ETH in the final state
        require(!isInFinalState);
        require(isInPreSaleState || isInRoundOneState || isInRoundTwoState);

        balances[owner] = balances[owner].sub(tokens_amount);
        balances[msg.sender] = balances[msg.sender].add(tokens_amount);
        emit PurchaseZanTokens(msg.sender, eth_sent, tokens_amount);
    }


// ------------------------------------------------------------------------
// Switches crowdsale stages: PreSale -> Round One -> Round Two
// ------------------------------------------------------------------------

function switchCrowdSaleStage() external onlyOwner {
        require(!isInFinalState && !isInRoundTwoState);

        if (!isInPreSaleState) {
            isInPreSaleState = true;
            exchangeRate = 1500;
            saleCap = (3 * 10**6) * (uint(10) ** decimals);
            emit SwitchCrowdSaleStage("PreSale", exchangeRate);
        }
        else if (!isInRoundOneState) {
            isInRoundOneState = true;
            exchangeRate = 1200;
            saleCap = saleCap + ((4 * 10**6) * (uint(10) ** decimals));
            emit SwitchCrowdSaleStage("RoundOne", exchangeRate);
        }
        else if (!isInRoundTwoState) {
            isInRoundTwoState = true;
            exchangeRate = 900;
            saleCap = saleCap + ((5 * 10**6) * (uint(10) ** decimals));
            emit SwitchCrowdSaleStage("RoundTwo", exchangeRate);
        }

        stateStartDate = now + 5 minutes;
        stateEndDate = stateStartDate + 7 days;
    }


/// @dev Transfers tokens to a specified address with additional data if the recipient is a contact.
/// @param to The address to transfer tokens to.
/// @param value The amount of tokens to be transferred.
/// @param data The extra data to be passed to the receiving contract.
/// @return A boolean that indicates if the operation was successful.

function transferAndCall(address to, uint256 value, bytes data) external canTransfer returns (bool) {
        require(to != address(this));
        require(super.transfer(to, value));
        emit Transfer(msg.sender, to, value, data);
        if (isContract(to)) {
            require(contractFallback(msg.sender, to, value, data));
        }
        return true;
    }


/// @dev Transfers tokens from one address to another.
/// @param from The address to transfer tokens from.
/// @param to The address to transfer tokens to.
/// @param value The amount of tokens to be transferred.
/// @return A boolean that indicates if the operation was successful.

function transferFrom(address from, address to, uint256 value) public canTransfer returns (bool) {
        require(super.transferFrom(from, to, value));
        if (isContract(to) && !contractFallback(from, to, value, new bytes(0))) {
            if (to == _bridgeContract) {
                revert();
            }
            emit ContractFallbackCallFailed(from, to, value);
        }
        return true;
    }


/// @dev Transfers tokens to a specified addresses (optimized version for initial sending tokens).
/// @param recipients Addresses to transfer tokens to.
/// @param values Amounts of tokens to be transferred.
/// @return A boolean that indicates if the operation was successful.

function airdrop(address[] recipients, uint256[] values) public canTransfer returns (bool) {
        require(recipients.length == values.length);
        uint256 senderBalance = _balances[msg.sender];
        for (uint256 i = 0; i < values.length; i++) {
            uint256 value = values[i];
            address to = recipients[i];
            require(senderBalance >= value);
            if (msg.sender != recipients[i]) {
                senderBalance = senderBalance - value;
                _balances[to] += value;
            }
            emit Transfer(msg.sender, to, value);
        }
        _balances[msg.sender] = senderBalance;
        return true;
    }


/// @dev Transfers funds stored on the token contract to specified recipient (required by token bridge).

function claimTokens(address token, address to) public onlyOwnerOrBridgeContract {
        require(to != address(0));
        if (token == address(0)) {
            to.transfer(address(this).balance);
        } else {
            ERC20 erc20 = ERC20(token);
            uint256 balance = erc20.balanceOf(address(this));
            require(erc20.transfer(to, balance));
        }
    }


// --- administration ---

function file(bytes32 what, address data) external auth {
        if (what == "outputConduit") { outputConduit = data; }
        else if (what == "jug") { jug = JugAbstract(data); }
        else revert("RwaUrn/unrecognised-param");
        emit File(what, data);
    }


// --- cdp operation ---
// n.b. that the operator must bring the gem

function lock(uint256 wad) external operator {
        require(wad <= 2**255 - 1, "RwaUrn/overflow");
        DSTokenAbstract(gemJoin.gem()).transferFrom(msg.sender, address(this), wad);
        // join with address this
        gemJoin.join(address(this), wad);
        vat.frob(gemJoin.ilk(), address(this), address(this), address(this), int(wad), 0);
        emit Lock(msg.sender, wad);
    }


// n.b. DAI can only go to the output conduit

function draw(uint256 wad) external operator {
        require(outputConduit != address(0));
        bytes32 ilk = gemJoin.ilk();
        jug.drip(ilk);
        (,uint256 rate,,,) = vat.ilks(ilk);
        uint256 dart = divup(mul(RAY, wad), rate);
        require(dart <= 2**255 - 1, "RwaUrn/overflow");
        vat.frob(ilk, address(this), address(this), address(this), 0, int(dart));
        daiJoin.exit(outputConduit, wad);
        emit Draw(msg.sender, wad);
    }


// n.b. anyone can wipe

function wipe(uint256 wad) external {
        daiJoin.join(address(this), wad);
        bytes32 ilk = gemJoin.ilk();
        jug.drip(ilk);
        (,uint256 rate,,,) = vat.ilks(ilk);
        uint256 dart = mul(RAY, wad) / rate;
        require(dart <= 2 ** 255, "RwaUrn/overflow");
        vat.frob(ilk, address(this), address(this), address(this), 0, -int(dart));
        emit Wipe(msg.sender, wad);
    }


/**
 * @dev Returns the amount of ether in wei that are equivalent to 1 unit (10 ** decimals) of asset
 * @param asset quoted currency
 * @return price in ether
 */

function getAssetToEthRate(address asset) public view returns (uint) {

    if (asset == ETH) {
      return 1 ether;
    }

    address aggregatorAddress = aggregators[asset];

    if (aggregatorAddress == address(0)) {
      revert("PriceFeedOracle: Oracle asset not found");
    }

    int rate = Aggregator(aggregatorAddress).latestAnswer();
    require(rate > 0, "PriceFeedOracle: Rate must be > 0");

    return uint(rate);
  }


// This function allows governance to take unsupported tokens out of the
// contract, since this one exists longer than the other pools.
// This is in an effort to make someone whole, should they seriously
// mess up. There is no guarantee governance will vote to return these.
// It also allows for removal of airdropped tokens.

function governanceRecoverUnsupported(IERC20 _token, uint256 amount, address to)
        external
    {
        // only gov
        require(msg.sender == owner(), "!governance");
        // cant take staked asset
        require(_token != uni_lp, "uni_lp");
        // cant take reward asset
        require(_token != yam, "yam");

        // transfer to
        _token.safeTransfer(to, amount);
    }


/**
 @dev constructor

 @param  _token              smart token governed by the converter
 @param  _registry           address of a contract registry contract
 @param  _maxConversionFee   maximum conversion fee, represented in ppm
 @param  _connectorToken     optional, initial connector, allows defining the first connector at deployment time
 @param  _connectorWeight    optional, weight for the initial connector
 */

function BancorConverter(
        ISmartToken _token,
        IContractRegistry _registry,
        uint32 _maxConversionFee,
        IERC20Token _connectorToken,
        uint32 _connectorWeight
    )
        public
        SmartTokenController(_token)
        validAddress(_registry)
        validMaxConversionFee(_maxConversionFee)
    {
        registry = _registry;
        IContractFeatures features = IContractFeatures(registry.getAddress(ContractIds.CONTRACT_FEATURES));

        // initialize supported features
        if (features != address(0))
            features.enableFeatures(FeatureIds.CONVERTER_CONVERSION_WHITELIST, true);

        maxConversionFee = _maxConversionFee;

        if (_connectorToken != address(0))
            addConnector(_connectorToken, _connectorWeight, false);
    }


/**
 @dev defines a new connector for the token
 can only be called by the owner while the converter is inactive

 @param _token                  address of the connector token
 @param _weight                 constant connector weight, represented in ppm, 1-1000000
 @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
 */

function addConnector(IERC20Token _token, uint32 _weight, bool _enableVirtualBalance)
        public
        ownerOnly
        inactive
        validAddress(_token)
        notThis(_token)
        validConnectorWeight(_weight)
    {
        require(_token != token && !connectors[_token].isSet && totalConnectorWeight + _weight <= MAX_WEIGHT); // validate input

        connectors[_token].virtualBalance = 0;
        connectors[_token].weight = _weight;
        connectors[_token].isVirtualBalanceEnabled = _enableVirtualBalance;
        connectors[_token].isPurchaseEnabled = true;
        connectors[_token].isSet = true;
        connectorTokens.push(_token);
        totalConnectorWeight += _weight;
    }


/**
 @dev updates one of the token connectors
 can only be called by the owner

 @param _connectorToken         address of the connector token
 @param _weight                 constant connector weight, represented in ppm, 1-1000000
 @param _enableVirtualBalance   true to enable virtual balance for the connector, false to disable it
 @param _virtualBalance         new connector's virtual balance
 */

function updateConnector(IERC20Token _connectorToken, uint32 _weight, bool _enableVirtualBalance, uint256 _virtualBalance)
        public
        ownerOnly
        validConnector(_connectorToken)
        validConnectorWeight(_weight)
    {
        Connector storage connector = connectors[_connectorToken];
        require(totalConnectorWeight - connector.weight + _weight <= MAX_WEIGHT); // validate input

        totalConnectorWeight = totalConnectorWeight - connector.weight + _weight;
        connector.weight = _weight;
        connector.isVirtualBalanceEnabled = _enableVirtualBalance;
        connector.virtualBalance = _virtualBalance;
    }


/**
 @dev returns the expected return for converting a specific amount of _fromToken to _toToken

 @param _fromToken  ERC20 token to convert from
 @param _toToken    ERC20 token to convert to
 @param _amount     amount to convert, in fromToken

 @return expected conversion return amount
 */

function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256) {
        require(_fromToken != _toToken); // validate input

        // conversion between the token and one of its connectors
        if (_toToken == token)
            return getPurchaseReturn(_fromToken, _amount);
        else if (_fromToken == token)
            return getSaleReturn(_toToken, _amount);

        // conversion between 2 connectors
        return getCrossConnectorReturn(_fromToken, _toToken, _amount);
    }


/**
 @dev returns the expected return for buying the token for a connector token

 @param _connectorToken  connector token contract address
 @param _depositAmount   amount to deposit (in the connector token)

 @return expected purchase return amount
 */

function getPurchaseReturn(IERC20Token _connectorToken, uint256 _depositAmount)
        public
        view
        active
        validConnector(_connectorToken)
        returns (uint256)
    {
        Connector storage connector = connectors[_connectorToken];
        require(connector.isPurchaseEnabled); // validate input

        uint256 tokenSupply = token.totalSupply();
        uint256 connectorBalance = getConnectorBalance(_connectorToken);
        IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
        uint256 amount = formula.calculatePurchaseReturn(tokenSupply, connectorBalance, connector.weight, _depositAmount);

        // return the amount minus the conversion fee
        return getFinalAmount(amount, 1);
    }


/**
 @dev returns the expected return for selling the token for one of its connector tokens

 @param _connectorToken  connector token contract address
 @param _sellAmount      amount to sell (in the smart token)

 @return expected sale return amount
 */

function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount)
        public
        view
        active
        validConnector(_connectorToken)
        returns (uint256)
    {
        Connector storage connector = connectors[_connectorToken];
        uint256 tokenSupply = token.totalSupply();
        uint256 connectorBalance = getConnectorBalance(_connectorToken);
        IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
        uint256 amount = formula.calculateSaleReturn(tokenSupply, connectorBalance, connector.weight, _sellAmount);

        // return the amount minus the conversion fee
        return getFinalAmount(amount, 1);
    }


/**
 @dev returns the expected return for selling one of the connector tokens for another connector token

 @param _fromConnectorToken  contract address of the connector token to convert from
 @param _toConnectorToken    contract address of the connector token to convert to
 @param _sellAmount          amount to sell (in the from connector token)

 @return expected sale return amount (in the to connector token)
 */

function getCrossConnectorReturn(IERC20Token _fromConnectorToken, IERC20Token _toConnectorToken, uint256 _sellAmount)
        public
        view
        active
        validConnector(_fromConnectorToken)
        validConnector(_toConnectorToken)
        returns (uint256)
    {
        Connector storage fromConnector = connectors[_fromConnectorToken];
        Connector storage toConnector = connectors[_toConnectorToken];
        require(toConnector.isPurchaseEnabled); // validate input

        uint256 fromConnectorBalance = getConnectorBalance(_fromConnectorToken);
        uint256 toConnectorBalance = getConnectorBalance(_toConnectorToken);

        IBancorFormula formula = IBancorFormula(registry.getAddress(ContractIds.BANCOR_FORMULA));
        uint256 amount = formula.calculateCrossConnectorReturn(fromConnectorBalance, fromConnector.weight, toConnectorBalance, toConnector.weight, _sellAmount);

        // return the amount minus the conversion fee
        // the fee is higher (magnitude = 2) since cross connector conversion equals 2 conversions (from / to the smart token)
        return getFinalAmount(amount, 2);
    }


/**
 @dev buys the token by depositing one of its connector tokens

 @param _connectorToken  connector token contract address
 @param _depositAmount   amount to deposit (in the connector token)
 @param _minReturn       if the conversion results in an amount smaller than the minimum return - it is cancelled, must be nonzero

 @return buy return amount
 */

function buy(IERC20Token _connectorToken, uint256 _depositAmount, uint256 _minReturn) internal returns (uint256) {
        uint256 amount = getPurchaseReturn(_connectorToken, _depositAmount);
        // ensure the trade gives something in return and meets the minimum requested amount
        require(amount != 0 && amount >= _minReturn);

        // update virtual balance if relevant
        Connector storage connector = connectors[_connectorToken];
        if (connector.isVirtualBalanceEnabled)
            connector.virtualBalance = safeAdd(connector.virtualBalance, _depositAmount);

        // transfer funds from the caller in the connector token
        assert(_connectorToken.transferFrom(msg.sender, this, _depositAmount));
        // issue new funds to the caller in the smart token
        token.issue(msg.sender, amount);

        // calculate conversion fee and dispatch the conversion event
        uint256 feeAmount = safeSub(amount, getFinalAmount(amount, 1));
        dispatchConversionEvent(_connectorToken, token, _depositAmount, amount, feeAmount);

        // dispatch price data update for the smart token/connector
        emit PriceDataUpdate(_connectorToken, token.totalSupply(), getConnectorBalance(_connectorToken), connector.weight);
        return amount;
    }


/**
 @dev helper, dispatches the Conversion event

 @param _fromToken       ERC20 token to convert from
 @param _toToken         ERC20 token to convert to
 @param _amount          amount purchased/sold (in the source token)
 @param _returnAmount    amount returned (in the target token)
 */

function dispatchConversionEvent(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _returnAmount, uint256 _feeAmount) private {
        // fee amount is converted to 255 bits -
        // negative amount means the fee is taken from the source token, positive amount means its taken from the target token
        // currently the fee is always taken from the target token
        // since we convert it to a signed number, we first ensure that it's capped at 255 bits to prevent overflow
        assert(_feeAmount <= 2 ** 255);
        emit Conversion(_fromToken, _toToken, msg.sender, _amount, _returnAmount, int256(_feeAmount));
    }


/**
 * @dev get '_account' stakes by page
 */

function getStakes(address _account, uint _index, uint _offset) external view returns (StakeSet.Item[] memory items) {
        uint totalSize = userOrders(_account);
        require(0 < totalSize && totalSize > _index, "getStakes: 0 < totalSize && totalSize > _index");
        uint offset = _offset;
        if (totalSize < _index + offset) {
            offset = totalSize - _index;
        }

        items = new StakeSet.Item[](offset);
        for (uint i = 0; i < offset; i++) {
            items[i] = _stakeOf[_account].at(_index + i);
        }
    }


// todo: get token usdt price from swap

function getUSDTPrice(address _token) public view returns (uint) {

        if (payToken == _token) {return 1 ether;}
        (bool success, bytes memory returnData) = lpAddress[_token].staticcall(abi.encodeWithSignature("getReserves()"));
        if (success) {
            (uint112 reserve0, uint112 reserve1, ) = abi.decode(returnData, (uint112, uint112, uint32));
            uint DECIMALS = 10**18;
            if(_token==aToken){
                DECIMALS = 10**6;
                //return uint(reserve1).mul(DECIMALS).div(uint(reserve0));
            }
            //return uint(reserve0).mul(DECIMALS).div(uint(reserve1));
            return uint(reserve1).mul(DECIMALS).div(uint(reserve0));
        }

        return 0;
    }


// function getHashrate(uint256 staticHashrate,uint m) private  pure returns (uint256 hashrate) {
//         if(m==0){
//             hashrate = staticHashrate.mul(18).div(100);
//         }else if(m==1){
//             hashrate = staticHashrate.mul(16).div(100);
//         }else if(m==2){
//             hashrate = staticHashrate.mul(14).div(100);
//         }else if(m==3){
//             hashrate = staticHashrate.mul(12).div(100);
//         }else if(m==4){
//             hashrate = staticHashrate.mul(10).div(100);
//         }else if(4<m&&m<=8){
//             hashrate = staticHashrate.mul(5).div(100);
//         }else if(8<m&&m<=12){
//             hashrate = staticHashrate.mul(2).div(100);
//         }
//     return hashrate;
// }
// function calcDynamicHashrate(uint256 staticHashrate,address user) private  returns (bool) {
//     address[] memory arr = new address[](13);
//     uint  i = 0;
//     while(_referees[user]!=address(0)&&i<13){
//             arr[i]=_referees[user];
//             user = _referees[user];
//             i++;
//     }
//     uint  m = 0;
//     uint256 totalHtate;
//     while(arr[m]!=address(0)&&m<13){
//         if(userOrders(arr[m])>0){
//             uint256 hrate = getHashrate(staticHashrate,m);
//              _dynamic_hashrate[arr[m]]=_dynamic_hashrate[arr[m]].add(hrate);
//             totalHtate = totalHtate.add(hrate);
//             address[] memory mygenerationarr=_mygeneration[arr[m]];
//             for(uint n = 0;n<mygenerationarr.length;n++){
//                 if(_vip[mygenerationarr[n]]==3){
//                     _dynamic_hashrate[mygenerationarr[n]]=_dynamic_hashrate[mygenerationarr[n]].add(hrate.mul(5).div(100));
//                     totalHtate = totalHtate.add(hrate.mul(5).div(100));
//                 }else  if(_vip[mygenerationarr[n]]==4){
//                     _dynamic_hashrate[mygenerationarr[n]]=_dynamic_hashrate[mygenerationarr[n]].add(hrate.mul(6).div(100));
//                     totalHtate = totalHtate.add(hrate.mul(6).div(100));
//                 }else  if(_vip[mygenerationarr[n]]==5){
//                     _dynamic_hashrate[mygenerationarr[n]]=_dynamic_hashrate[mygenerationarr[n]].add(hrate.mul(7).div(100));
//                     totalHtate = totalHtate.add(hrate.mul(7).div(100));
//                 }else  if(_vip[mygenerationarr[n]]==6){
//                     _dynamic_hashrate[mygenerationarr[n]]=_dynamic_hashrate[mygenerationarr[n]].add(hrate.mul(8).div(100));
//                     totalHtate = totalHtate.add(hrate.mul(8).div(100));
//                 }
//             }
//         }
//         m++;
//     }
//     _total_dynamic_hashrate= _total_dynamic_hashrate.add(totalHtate);
//     return true;
// }

function levelCostU(uint256 value,uint256 vip) public pure returns(uint256 u) {
        require(value<=6&&value>vip, "levelCostU: vip false");
            if(value==1){
                u=100;
            }else if(value==2){
                if(vip==0){
                    u=300;
                }else{
                    u=200;
                }
            }else if(value==3){
                if(vip==0){
                    u=500;
                }else if(vip==1){
                    u=400;
                }else{
                    u=200;
                }
            }else if(value==4){
                if(vip==0){
                    u=700;
                }else if(vip==1){
                    u=600;
                }else if(vip==2){
                    u=400;
                }else{
                    u=200;
                }
            }else if(value==5){
                if(vip==0){
                    u=1000;
                }else if(vip==1){
                    u=900;
                }else if(vip==2){
                    u=700;
                }else if(vip==3){
                    u=500;
                }else{
                    u=300;
                }
            }else{
                if(vip==0){
                    u=1500;
                }else if(vip==1){
                    u=1400;
                }else if(vip==2){
                    u=1200;
                }else if(vip==3){
                    u=1000;
                }else if(vip==4){
                    u=800;
                }else{
                     u=500;
                }
            }
    }


// https://uniswap.org/docs/v2/smart-contract-integration/getting-pair-addresses/

function _pairFor(address _factory, address tokenA, address tokenB) internal pure returns (address pair) {

    // sort tokens
    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);

    require(token0 != token1, "TWAP: identical addresses");
    require(token0 != address(0), "TWAP: zero address");

    pair = address(uint(keccak256(abi.encodePacked(
        hex'ff',
        _factory,
        keccak256(abi.encodePacked(token0, token1)),
        hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
      ))));
  }


/* update */

function update(address[] calldata pairs) external {

    for (uint i = 0; i < pairs.length; i++) {

      // note: not reusing canUpdate() because we need the bucket variable
      address pair = pairs[i];
      uint index = timestampToIndex(block.timestamp);
      Bucket storage bucket = buckets[pair][index];

      if (block.timestamp - bucket.timestamp < periodSize) {
        continue;
      }

      (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair);
      bucket.timestamp = block.timestamp;
      bucket.price0Cumulative = price0Cumulative;
      bucket.price1Cumulative = price1Cumulative;

      emit Updated(pair, block.timestamp, price0Cumulative, price1Cumulative);
    }
  }


/* consult */

function _getCumulativePrices(
    address tokenIn,
    address tokenOut
  ) internal view returns (uint priceCumulativeStart, uint priceCumulativeEnd, uint timeElapsed) {

    uint currentIndex = timestampToIndex(block.timestamp);
    uint firstBucketIndex = (currentIndex + 1) % periodsPerWindow;

    address pair = _pairFor(factory, tokenIn, tokenOut);
    Bucket storage firstBucket = buckets[pair][firstBucketIndex];

    timeElapsed = block.timestamp - firstBucket.timestamp;
    require(timeElapsed <= windowSize, "TWAP: missing historical reading");
    require(timeElapsed >= windowSize - periodSize * 2, "TWAP: unexpected time elapsed");

    (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair);

    if (tokenIn < tokenOut) {
      return (firstBucket.price0Cumulative, price0Cumulative, timeElapsed);
    }

    return (firstBucket.price1Cumulative, price1Cumulative, timeElapsed);
  }


/**
 *  @dev Returns the amount out corresponding to the amount in for a given token using the
 *  @dev   moving average over the time range [now - [windowSize, windowSize - periodSize * 2], now]
 *  @dev   update must have been called for the bucket corresponding to timestamp `now - windowSize`
 */

function consult(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut) {

    uint pastPriceCumulative;
    uint currentPriceCumulative;
    uint timeElapsed;

    (pastPriceCumulative, currentPriceCumulative, timeElapsed) = _getCumulativePrices(tokenIn, tokenOut);

    return _computeAmountOut(
      pastPriceCumulative,
      currentPriceCumulative,
      timeElapsed,
      amountIn
    );
  }


/** @dev Calls createDispute function of the specified arbitrator to create a dispute.
 *  @param _arbitrator The arbitrator of prospective dispute.
 *  @param _arbitratorExtraData Extra data for the arbitrator of prospective dispute.
 *  @param _metaevidenceURI Link to metaevidence of prospective dispute.
 */

function createDispute(Arbitrator _arbitrator, bytes calldata _arbitratorExtraData, string calldata _metaevidenceURI) external payable {
        uint arbitrationCost = _arbitrator.arbitrationCost(_arbitratorExtraData);
        require(msg.value >= arbitrationCost, "Insufficient message value.");
        uint disputeID = _arbitrator.createDispute.value(arbitrationCost)(NUMBER_OF_CHOICES, _arbitratorExtraData);

        uint localDisputeID = disputes.length++;
        DisputeStruct storage dispute = disputes[localDisputeID];
        dispute.arbitrator = _arbitrator;
        dispute.arbitratorExtraData = _arbitratorExtraData;
        dispute.disputeIDOnArbitratorSide = disputeID;
        dispute.rounds.length++;

        arbitratorExternalIDtoLocalID[address(_arbitrator)][disputeID] = localDisputeID;

        emit MetaEvidence(localDisputeID, _metaevidenceURI);
        emit Dispute(_arbitrator, disputeID, localDisputeID, localDisputeID);

        msg.sender.send(msg.value-arbitrationCost);
    }


/** @dev Manages contributions and calls appeal function of the specified arbitrator to appeal a dispute. This function lets appeals be crowdfunded.
 *  @param _localDisputeID Index of the dispute in disputes array.
 *  @param _party The side to which the caller wants to contribute.
 */

function appeal(uint _localDisputeID, Party _party) external payable {
        require(_party != Party.RefuseToArbitrate, "You can't fund an appeal in favor of refusing to arbitrate.");
        uint8 side = uint8(_party);
        DisputeStruct storage dispute = disputes[_localDisputeID];

        (uint appealPeriodStart, uint appealPeriodEnd) = dispute.arbitrator.appealPeriod(dispute.disputeIDOnArbitratorSide);
        require(now >= appealPeriodStart && now < appealPeriodEnd, "Funding must be made within the appeal period.");

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

        require(!round.hasPaid[side], "Appeal fee has already been paid");
        round.hasPaid[side] = true; // Temporarily assume the contribution covers the missing amount to block re-entrancy.

        uint appealCost = dispute.arbitrator.appealCost(dispute.disputeIDOnArbitratorSide, dispute.arbitratorExtraData);

        uint contribution;

        if(round.paidFees[side] + msg.value >= appealCost){
          contribution = appealCost - round.paidFees[side];
        }
        else{
            contribution = msg.value;
            round.hasPaid[side] = false; // Rollback the temporary assumption above.
        }
        msg.sender.send(msg.value - contribution);
        round.contributions[msg.sender][side] += contribution;
        round.paidFees[side] += contribution;
        round.totalAppealFeesCollected += contribution;

        if(round.hasPaid[requester] && round.hasPaid[respondent]){
            dispute.arbitrator.appeal.value(appealCost)(dispute.disputeIDOnArbitratorSide, dispute.arbitratorExtraData);
            dispute.rounds.length++;
            round.totalAppealFeesCollected = round.totalAppealFeesCollected.subCap(appealCost);
        }
    }


/** @dev To be called by the arbitrator of the dispute, to declare winning side.
 *  @param _externalDisputeID ID of the dispute in arbitrator contract.
 *  @param _ruling The side to which the caller wants to contribute.
 */

function rule(uint _externalDisputeID, uint _ruling) external {
        uint _localDisputeID = arbitratorExternalIDtoLocalID[msg.sender][_externalDisputeID];
        DisputeStruct storage dispute = disputes[_localDisputeID];
        require(msg.sender == address(dispute.arbitrator), "Unauthorized call.");
        require(_ruling <= NUMBER_OF_CHOICES, "Invalid ruling.");
        require(dispute.isRuled == false, "Is ruled already.");

        dispute.isRuled = true;
        dispute.judgment = Party(_ruling);

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

        uint resultRuling = _ruling;
        if (round.hasPaid[requester] == true) // If one side paid its fees, the ruling is in its favor. Note that if the other side had also paid, an appeal would have been created.
            resultRuling = 1;
        else if (round.hasPaid[respondent] == true)
            resultRuling = 2;

        emit Ruling(Arbitrator(msg.sender), dispute.disputeIDOnArbitratorSide, resultRuling);
    }


/* Initializes the contract */

function token(uint256 initialSupply, string tokenName, uint8 decimalUnits, string tokenSymbol) internal {
        balanceOf[msg.sender] = initialSupply; // Gives the creator all initial tokens.
        totalSupply           = initialSupply; // Update total supply.
        name                  = tokenName;     // Set the name for display purposes.
        symbol                = tokenSymbol;   // Set the symbol for display purposes.
        decimals              = decimalUnits;  // Amount of decimals for display purposes.
    }


/// @notice Send `_value` tokens to `_to` in behalf of `_from`.
/// @param _from The address of the sender.
/// @param _to The address of the recipient.
/// @param _value The amount to send.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]); // Check allowance.
        allowance[_from][msg.sender] -= _value; // Updates the allowance array, substracting the amount sent.
        _transfer(_from, _to, _value); // Makes the transfer.
        return true;
    }


/// @notice Allows `_spender` to spend a maximum of `_value` tokens in your behalf.
/// @param _spender The address authorized to spend.
/// @param _value The max amount they can spend.

function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value; // Adds a new register to allowance, permiting _spender to use _value of your tokens.
        return true;
    }


/* Overrides basic transfer function due to comision value */

function transfer(address _to, uint256 _value) public {
    	// This function requires a comision value of 0.4% of the market value.
        uint market_value = _value * sellPrice;
        uint comision = market_value * 4 / 1000;
        // The token smart-contract pays comision, else the transfer is not possible.
        require(this.balance >= comision);
        comisionGetter.transfer(comision); // Transfers comision to the comisionGetter.
        _transfer(msg.sender, _to, _value);
    }


/* Internal, updates the profit value */

function _updateProfit(uint256 _increment, bool add) internal{
        if (add){
            // Increase the profit value
            profit = profit + _increment;
        }else{
            // Decrease the profit value
            if(_increment > profit){ profit = 0; }
            else{ profit = profit - _increment; }
        }
    }


/// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens.
/// @param target Address to be frozen.
/// @param freeze Either to freeze target or not.

function freezeAccount(address target, bool freeze) onlyOwner public {
        frozenAccount[target] = freeze; // Sets the target status. True if it's frozen, False if it's not.
        FrozenFunds(target, freeze); // Notifies the blockchain about the change of state.
    }


/// @notice Deposits Ether to the contract

function deposit() payable public returns(bool success) {
        require((this.balance + msg.value) > this.balance); // Checks for overflows.
        //Contract has already received the Ether when this function is executed.
        _updateSolvency(this.balance);   // Updates the solvency value of the contract.
        _updateProfit(msg.value, false); // Decrease profit value.
        // Decrease because deposits will be done mostly by the owner.
        // Possible donations won't count as profit for the company, but in favor of the investors.
        LogDeposit(msg.sender, msg.value); // Notifies the blockchain about the Ether received.
        return true;
    }


/// @notice The owner withdraws Ether from the contract.
/// @param amountInWeis Amount of ETH in WEI which will be withdrawed.

function withdraw(uint amountInWeis) onlyOwner public {
        LogWithdrawal(msg.sender, amountInWeis); // Notifies the blockchain about the withdrawal.
        _updateSolvency( (this.balance - amountInWeis) ); // Updates the solvency value of the contract.
        _updateProfit(amountInWeis, true);                // Increase the profit value.
        owner.transfer(amountInWeis); // Sends the Ether to owner address.
    }


// For pushing pre-ICO records

function push(address buyer, uint256 amount) public onlyOwner { //b753a98c
        require(balances[wallet] >= amount);
        require(now < startDate);
        require(buyer != wallet);

        preICO_address[ buyer ] = true;

        // Transfer
        balances[wallet] = balances[wallet].sub(amount);
        balances[buyer] = balances[buyer].add(amount);
        PreICOTokenPushed(buyer, amount);
    }


/// @dev Mature the fyToken by recording the chi.

function _mature()
        private
        returns (uint256 _chiAtMaturity)
    {
        (_chiAtMaturity,) = oracle.get(underlyingId, CHI, 0);   // The value returned is an accumulator, it doesn't need an input amount
        chiAtMaturity = _chiAtMaturity;
        emit SeriesMatured(_chiAtMaturity);
    }


/// @dev Retrieve the chi accrual since maturity, maturing if necessary.
/// Note: Call only after checking we are past maturity

function _accrual()
        private
        returns (uint256 accrual_)
    {
        if (chiAtMaturity == CHI_NOT_SET) {  // After maturity, but chi not yet recorded. Let's record it, and accrual is then 1.
            _mature();
        } else {
            (uint256 chi,) = oracle.get(underlyingId, CHI, 0);   // The value returned is an accumulator, it doesn't need an input amount
            accrual_ = chi.wdiv(chiAtMaturity);
        }
        accrual_ = accrual_ >= 1e18 ? accrual_ : 1e18;     // The accrual can't be below 1 (with 18 decimals)
    }


/// @dev Burn fyToken after maturity for an amount that increases according to `chi`
/// If `amount` is 0, the contract will redeem instead the fyToken balance of this contract. Useful for batches.

function redeem(address to, uint256 amount)
        external override
        afterMaturity
        returns (uint256 redeemed)
    {
        uint256 amount_ = (amount == 0) ? _balanceOf[address(this)] : amount;
        _burn(msg.sender, amount_);
        redeemed = amount_.wmul(_accrual());
        join.exit(to, redeemed.u128());

        emit Redeemed(msg.sender, to, amount_, redeemed);
    }


/// @dev Burn fyTokens.
/// Any tokens locked in this contract will be burned first and subtracted from the amount to burn from the user's wallet.
/// This feature allows someone to transfer fyToken to this contract to enable a `burn`, potentially saving the cost of `approve` or `permit`.

function _burn(address from, uint256 amount)
        internal override
        returns (bool)
    {
        // First use any tokens locked in this contract
        uint256 available = _balanceOf[address(this)];
        if (available >= amount) {
            return super._burn(address(this), amount);
        } else {
            if (available > 0 ) super._burn(address(this), available);
            unchecked { _decreaseAllowance(from, amount - available); }
            unchecked { return super._burn(from, amount - available); }
        }
    }


/**
 * @dev From ERC-3156. Loan `amount` fyDai to `receiver`, which needs to return them plus fee to this contract within the same transaction.
 * Note that if the initiator and the borrower are the same address, no approval is needed for this contract to take the principal + fee from the borrower.
 * If the borrower transfers the principal + fee to this contract, they will be burnt here instead of pulled from the borrower.
 * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
 * @param token The loan currency. Must be a fyDai contract.
 * @param amount The amount of tokens lent.
 * @param data A data parameter to be passed on to the `receiver` for any custom use.
 */

function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes memory data)
        external override
        beforeMaturity
        returns(bool)
    {
        require(token == address(this), "Unsupported currency");
        _mint(address(receiver), amount);
        require(receiver.onFlashLoan(msg.sender, token, amount, 0, data) == FLASH_LOAN_RETURN, "Non-compliant borrower");
        _burn(address(receiver), amount);
        return true;
    }


// public (Early Bird Mint) 150 to mint

function earlyBirdMint() public payable {
        uint256 supply = totalSupply();
        require(
            earlyBirdMinting,
            "Error: Early Bird minting is not active yet."
        );
        require(supply < 250, "Error: Maximum limit surpassed"); // max total mints is 100 marketing, 150 earlybird,

        require(
            earlyBirdList[msg.sender],
            "Error: Sender is not on the Early Bird List!"
        );

        if (mintLimitActive) {
            require(
                !alreadyMinted[msg.sender],
                "Error: Sender already minted one token"
            );
        }

        if (msg.sender != owner()) {
            require(msg.value >= cost, "Error: costs are higher per NFT.");
        }

        _safeMint(msg.sender, supply + 1);
        alreadyMinted[msg.sender] = true;
    }


// public marketing mint 100

function marketingMint() public payable onlyOwnerOrDev {
        uint256 supply = totalSupply();
        require(
            marketingMinting,
            "Error: Marketing minting is not active yet."
        );
        require(supply < 100, "Error you may only mint 100 NFTs"); // max total mints is 100 marketing

        for (uint256 i = 1; i <= 100; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }


/**
 * @dev is used to add initital advisory board members
 * @param abArray is the list of initial advisory board members
 */

function addInitialABMembers(address[] calldata abArray) external onlyOwner {

    //Ensure that NXMaster has initialized.
    require(ms.masterInitialized());

    require(maxABCount >=
      SafeMath.add(numberOfMembers(uint(Role.AdvisoryBoard)), abArray.length)
    );
    //AB count can't exceed maxABCount
    for (uint i = 0; i < abArray.length; i++) {
      require(checkRole(abArray[i], uint(MemberRoles.Role.Member)));
      _updateRole(abArray[i], uint(Role.AdvisoryBoard), true);
    }
  }


/**
 * @dev to add members before launch
 * @param userArray is list of addresses of members
 * @param tokens is list of tokens minted for each array element
 */

function addMembersBeforeLaunch(address[] memory userArray, uint[] memory tokens) public onlyOwner {
    require(!launched);

    for (uint i = 0; i < userArray.length; i++) {
      require(!ms.isMember(userArray[i]));
      dAppToken.addToWhitelist(userArray[i]);
      _updateRole(userArray[i], uint(Role.Member), true);
      dAppToken.mint(userArray[i], tokens[i]);
    }
    launched = true;
    launchedOn = now;

  }


/**
 * @dev Called by user to pay joining membership fee
 */

function payJoiningFee(address _userAddress) public payable {
    require(_userAddress != address(0));
    require(!ms.isPause(), "Emergency Pause Applied");
    if (msg.sender == address(ms.getLatestAddress("QT"))) {
      require(td.walletAddress() != address(0), "No walletAddress present");
      dAppToken.addToWhitelist(_userAddress);
      _updateRole(_userAddress, uint(Role.Member), true);
      td.walletAddress().transfer(msg.value);
    } else {
      require(!qd.refundEligible(_userAddress));
      require(!ms.isMember(_userAddress));
      require(msg.value == td.joiningFee());
      qd.setRefundEligible(_userAddress, true);
    }
  }


/**
 * @dev to perform kyc verdict
 * @param _userAddress whose kyc is being performed
 * @param verdict of kyc process
 */

function kycVerdict(address payable _userAddress, bool verdict) public {

    require(msg.sender == qd.kycAuthAddress());
    require(!ms.isPause());
    require(_userAddress != address(0));
    require(!ms.isMember(_userAddress));
    require(qd.refundEligible(_userAddress));
    if (verdict) {
      qd.setRefundEligible(_userAddress, false);
      uint fee = td.joiningFee();
      dAppToken.addToWhitelist(_userAddress);
      _updateRole(_userAddress, uint(Role.Member), true);
      td.walletAddress().transfer(fee); // solhint-disable-line

    } else {
      qd.setRefundEligible(_userAddress, false);
      _userAddress.transfer(td.joiningFee()); // solhint-disable-line
    }
  }


/**
 * @dev Called by existed member if wish to Withdraw membership.
 */

function withdrawMembership() public {

    require(!ms.isPause() && ms.isMember(msg.sender));
    require(dAppToken.totalLockedBalance(msg.sender, now) == 0); // solhint-disable-line
    require(!tf.isLockedForMemberVote(msg.sender)); // No locked tokens for Member/Governance voting
    require(cr.getAllPendingRewardOfUser(msg.sender) == 0); // No pending reward to be claimed(claim assesment).
    require(dAppToken.tokensUnlockable(msg.sender, "CLA") == 0, "Member should have no CLA unlockable tokens");

    gv.removeDelegation(msg.sender);
    dAppToken.burnFrom(msg.sender, tk.balanceOf(msg.sender));
    _updateRole(msg.sender, uint(Role.Member), false);
    dAppToken.removeFromWhitelist(msg.sender); // need clarification on whitelist

    if (claimPayoutAddress[msg.sender] != address(0)) {
      claimPayoutAddress[msg.sender] = address(0);
      emit ClaimPayoutAddressSet(msg.sender, address(0));
    }
  }


/**
 * @dev Called by existed member if wish to switch membership to other address.
 * @param _add address of user to forward membership.
 */

function switchMembership(address _add) external {

    require(!ms.isPause() && ms.isMember(msg.sender) && !ms.isMember(_add));
    require(dAppToken.totalLockedBalance(msg.sender, now) == 0); // solhint-disable-line
    require(!tf.isLockedForMemberVote(msg.sender)); // No locked tokens for Member/Governance voting
    require(cr.getAllPendingRewardOfUser(msg.sender) == 0); // No pending reward to be claimed(claim assesment).
    require(dAppToken.tokensUnlockable(msg.sender, "CLA") == 0, "Member should have no CLA unlockable tokens");

    gv.removeDelegation(msg.sender);
    dAppToken.addToWhitelist(_add);
    _updateRole(_add, uint(Role.Member), true);
    tk.transferFrom(msg.sender, _add, tk.balanceOf(msg.sender));
    _updateRole(msg.sender, uint(Role.Member), false);
    dAppToken.removeFromWhitelist(msg.sender);

    address payable previousPayoutAddress = claimPayoutAddress[msg.sender];

    if (previousPayoutAddress != address(0)) {

      address payable storedAddress = previousPayoutAddress == _add ? address(0) : previousPayoutAddress;

      claimPayoutAddress[msg.sender] = address(0);
      claimPayoutAddress[_add] = storedAddress;

      // emit event for old address reset
      emit ClaimPayoutAddressSet(msg.sender, address(0));

      if (storedAddress != address(0)) {
        // emit event for setting the payout address on the new member address if it's non zero
        emit ClaimPayoutAddressSet(_add, storedAddress);
      }
    }

    emit switchedMembership(msg.sender, _add, now);
  }


/// @dev Gets the member addresses assigned by a specific role
/// @param _memberRoleId Member role id
/// @return roleId Role id
/// @return allMemberAddress Member addresses of specified role id

function members(uint _memberRoleId) public view returns (uint, address[] memory memberArray) {//solhint-disable-line
    uint length = memberRoleData[_memberRoleId].memberAddress.length;
    uint i;
    uint j = 0;
    memberArray = new address[](memberRoleData[_memberRoleId].memberCounter);
    for (i = 0; i < length; i++) {
      address member = memberRoleData[_memberRoleId].memberAddress[i];
      if (memberRoleData[_memberRoleId].memberActive[member] && !_checkMemberInArray(member, memberArray)) {//solhint-disable-line
        memberArray[j] = member;
        j++;
      }
    }

    return (_memberRoleId, memberArray);
  }


/// @dev Get All role ids array that has been assigned to a member so far.

function roles(address _memberAddress) public view returns (uint[] memory) {//solhint-disable-line
    uint length = memberRoleData.length;
    uint[] memory assignedRoles = new uint[](length);
    uint counter = 0;
    for (uint i = 1; i < length; i++) {
      if (memberRoleData[i].memberActive[_memberAddress]) {
        assignedRoles[counter] = i;
        counter++;
      }
    }
    return assignedRoles;
  }


/**
 * @dev to update the member roles
 * @param _memberAddress in concern
 * @param _roleId the id of role
 * @param _active if active is true, add the member, else remove it
 */

function _updateRole(address _memberAddress,
    uint _roleId,
    bool _active) internal {
    // require(_roleId != uint(Role.TokenHolder), "Membership to Token holder is detected automatically");
    if (_active) {
      require(!memberRoleData[_roleId].memberActive[_memberAddress]);
      memberRoleData[_roleId].memberCounter = SafeMath.add(memberRoleData[_roleId].memberCounter, 1);
      memberRoleData[_roleId].memberActive[_memberAddress] = true;
      memberRoleData[_roleId].memberAddress.push(_memberAddress);
    } else {
      require(memberRoleData[_roleId].memberActive[_memberAddress]);
      memberRoleData[_roleId].memberCounter = SafeMath.sub(memberRoleData[_roleId].memberCounter, 1);
      delete memberRoleData[_roleId].memberActive[_memberAddress];
    }
  }


/**
 * @dev to check if member is in the given member array
 * @param _memberAddress in concern
 * @param memberArray in concern
 * @return boolean to represent the presence
 */

function _checkMemberInArray(
    address _memberAddress,
    address[] memory memberArray
  )
  internal
  pure
  returns (bool memberExists)
  {
    uint i;
    for (i = 0; i < memberArray.length; i++) {
      if (memberArray[i] == _memberAddress) {
        memberExists = true;
        break;
      }
    }
  }


/**
 * @dev to add initial member roles
 * @param _firstAB is the member address to be added
 * @param memberAuthority is the member authority(role) to be added for
 */

function _addInitialMemberRoles(address _firstAB, address memberAuthority) internal {
    maxABCount = 5;
    _addRole("Unassigned", "Unassigned", address(0));
    _addRole(
      "Advisory Board",
      "Selected few members that are deeply entrusted by the dApp. An ideal advisory board should be a mix of skills of domain, governance, research, technology, consulting etc to improve the performance of the dApp.", //solhint-disable-line
      address(0)
    );
    _addRole(
      "Member",
      "Represents all users of Mutual.", //solhint-disable-line
      memberAuthority
    );
    _addRole(
      "Owner",
      "Represents Owner of Mutual.", //solhint-disable-line
      address(0)
    );
    // _updateRole(_firstAB, uint(Role.AdvisoryBoard), true);
    _updateRole(_firstAB, uint(Role.Owner), true);
    // _updateRole(_firstAB, uint(Role.Member), true);
    launchedOn = 0;
  }


/**
 * @dev Stake rPEPE-ETH LP tokens
 *
 * Requirement
 *
 * - In this pool, don't care about 2.5% fee for stake/unstake
 *
 * @param amount: Amount of LP tokens to deposit
 */

function stake(uint256 amount) public {
        require(amount > 0, "Staking amount must be more than zero");
        // Transfer tokens from staker to the contract amount
        require(IUniswapV2Pair(_UniswapV2Pair).transferFrom(_msgSender(), address(this), uint(amount)), "It has failed to transfer tokens from staker to contract.");
        // add staker to array
        if (_stakedAmount[_msgSender()] == 0) {
            _stakers.push(_msgSender());
        }
        // Increase the total staked amount
        _totalStakedAmount = _totalStakedAmount.add(amount);
        // Add new stake amount
        _stakedAmount[_msgSender()] = _stakedAmount[_msgSender()].add(amount);
        emit Staked(_msgSender(), amount);
    }


/**
 * @dev count and return pepe amount from lp token amount in uniswap v2 pool
 *
 * Formula
 *
 * - rPEPE = (staked LP / total LP in uniswap pool) * rPEPE in uniswap pool
 */

function _getStakedPepeAmount(uint256 amount) internal view returns (uint256)  {
        (uint112 pepeAmount,,) = IUniswapV2Pair(_UniswapV2Pair).getReserves();
        // get the total amount of LP token in uniswap v2 pool
        uint totalAmount = IUniswapV2Pair(_UniswapV2Pair).totalSupply();
        return amount.mul(uint256(pepeAmount)).div(uint256(totalAmount));
    }


// Standard mint function

function mint(uint256 _amount) public payable {
        uint256 supply = totalSupply();
        require( saleIsActive,                     "Sale isn't active" );
        require( _amount > 0 && _amount < 16,     "Can only mint between 1 and 15 tokens at once" );
        require( (supply + _amount) <= (MAX_SUPPLY - reserved), "Purchase would exceed maximum supply" );
        require( msg.value == price * _amount,   "Wrong amount of ETH sent" );
        for(uint256 i; i < _amount; i++){
            _safeMint( msg.sender, supply + i );
        }
    }


/**
 * @notice withdraw any Dai accumulated as dividends, and any tokens that might have been erroneously sent to this contract
 * @param token      address of token to withdraw
 * @param amount     amount of token to withdraw
 * @return true if success
 */

function withdrawERC20(address token, uint256 amount) external returns (bool) {
        require(msg.sender == _poolSource, "Only designated source can withdraw any token");
        require(token != address(_wToken), "Cannot withdraw asset token this way");

        IERC20(token).safeTransfer(_poolSource, amount);
        return true;
    }


/**
 * @notice deposit Dai and get back wTokens
 * @param amount      amount of Dai to deposit
 * @return true if success
 */

function swapWithDai(uint256 amount) external returns (bool) {
        require(amount > 0, "Dai amount must be greater than 0");

        uint256 tokenAmount = daiToToken(amount);

        // through not strictly needed, useful to have a clear message for this error case
        require(_wToken.balanceOf(address(this)) >= tokenAmount, "Insufficient token supply in the pool");

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

        emit TokenTransaction(msg.sender, msg.sender, tokenAmount, 1, amount);
        return true;
    }


/**
 * @notice deposit USDT and get back wTokens
 * @param amount      amount of USDT to deposit
 * @return true if success
 */

function swapWithUSDT(uint256 amount) external returns (bool) {
        require(amount > 0, "USDT amount must be greater than 0");

        uint256 tokenAmount = usdtToToken(amount);

        require(_wToken.balanceOf(address(this)) >= tokenAmount, "Insufficient token supply in the pool");

        // safeTransferFrom is necessary for USDT due to argument byte size check in USDT's transferFrom function
        _usdtContract.safeTransferFrom(msg.sender, _poolSource, amount);
        _wToken.transfer(msg.sender, tokenAmount);

        emit TokenTransaction(msg.sender, msg.sender, tokenAmount, 3, amount);
        return true;
    }


/// @notice A public call to increase the peg by 4%. Can only be called once every 12 hours, and only if
///         there is there is <= 2% as much Ether value in the contract as there is Dai value.
///         Example: with a peg of 100, and 10000 total Dai in the vault, the ether balance of the
///         vault must be less than or equal to 2 to execute this function.

function increasePeg() external {
        // check that the total value of eth in contract is <= 2% the total value of dai in the contract
        require (address(this).balance.mul(etherPeg) <= daiContract.balanceOf(address(this)).div(50));
        // check that peg hasn't been changed in last 12 hours
        require (now > pegMoveReadyTime);
        // increase peg
        etherPeg = etherPeg.mul(104).div(100);
        // reset cooldown
        pegMoveReadyTime = now+pegMoveCooldown;
    }


/// @notice All units are minimum denomination, ie base unit *10**-18
///         Use this payable function to buy INCH from the contract with Wei. Rates are based on premium
///         and etherPeg. For every Wei deposited, msg.sender recieves etherPeg/(0.000001*premium) INCH.
///         Example: If the peg is 100, and the premium is 2000000, msg.sender will recieve 50 INCH.
/// @dev    Transaction reverts if payment results in 0 INCH returned

function __buyInchWithWei() external payable {
        // Calculate the amount of inch give to msg.sender
        uint _inchToBuy = msg.value.mul(etherPeg).mul(premiumDigits).div(premium);
        // Require that INCH returned is not 0
        require(_inchToBuy > 0);
        // Transfer INCH to Purchaser
        inchWormContract.transfer(msg.sender, _inchToBuy);

        emit BuyInchWithWei(_inchToBuy, msg.value);
    }


/// @param  _inchToBuy: amount of INCH (in mininum denomination) msg.sender wishes to purchase
/// @notice All units are in minimum denomination, ie base unit *10**-18
///         Use this payable to buy INCH from the contract using Dai. Rates are based on premium.
///         For every Dai deposited, msg.sender recieves 1/(0.000001*premium) INCH.
///         Example: If the premium is 5000000, calling the function with input 1 will result in
///         msg.sender paying 5 DaiSats.

function __buyInchWithDai(uint _inchToBuy) external {
        // Calculate the amount of Dai to extract from the purchaser's wallet based on the premium
        uint _daiOwed = _inchToBuy.mul(premium).div(premiumDigits);
        // Take Dai from the purchaser and transfer to vault contract
        daiContract.transferFrom(msg.sender, address(this), _daiOwed);
        // Send INCH to purchaser
        inchWormContract.transfer(msg.sender, _inchToBuy);

        emit BuyInchWithDai(_inchToBuy, _daiOwed);
    }


// join presale - just send ETH to contract,
// remember to check GAS LIMIT > 70000!

receive() external payable {
        // only if not ended
        require(!presaleEnded, "Presale ended");
        // only if within time limit
        require(block.timestamp < presaleEnd, "Presale time's up");

        // record new user balance if possible
        uint256 amount = msg.value + balances[msg.sender];
        require(amount >= minPerUser, "Below buy-in");
        require(amount <= maxPerUser, "Over buy-in");
        balances[msg.sender] = amount;

        // end presale if reached limit
        if (collected() >= presaleLimit) {
            presaleEnded = true;
        }
    }


// withdraw ETH from contract
// can be used by user and owner
// return false if nothing to do

function withdraw() external returns (bool) {
        if (!presaleEnded) {
            // end and fail presale if failsafe time passed
            if (block.timestamp > presaleEnd + failSafeTime) {
                presaleEnded = true;
                presaleFailed = true;
                // don't return true, you can withdraw in this call
            }
        }
        // owner withdraw - presale succeed ?
        else if (msg.sender == owner && !presaleFailed) {
            send(owner, address(this).balance);
            return true;
        }

        // presale failed, withdraw to calling user
        if (presaleFailed) {
            uint256 amount = balances[msg.sender];
            balances[msg.sender] = 0;
            send(msg.sender, amount);
            return true;
        }

        // did nothing
        return false;
    }


// Send {tokens} amount of tokens from address {from} to address {to}
// The transferFrom method is used for a withdraw workflow, allowing contracts to send
// tokens on your behalf

function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        require( allowed[from][msg.sender] >= tokens && balances[from] >= tokens && tokens > 0 );
        balances[from] -= tokens;
        allowed[from][msg.sender] -= tokens;
        balances[to] += tokens;
        emit Transfer(from, to, tokens);
        return true;
    }


// automated buyback

function autoBuyBack(uint256 amountInWei) internal {

        lastAutoBuyBackTime = block.timestamp;

        address[] memory path = new address[](2);
        path[0] = dexRouter.WETH();
        path[1] = address(this);

        // make the swap
        dexRouter.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amountInWei}(
            0, // accept any amount of Ethereum
            path,
            address(0xdead),
            block.timestamp
        );

        emit BuyBackTriggered(amountInWei);
    }


/* ------------- Traits ------------- */

function encode(string[] memory strs) internal pure returns (bytes memory) {
        bytes memory a;
        for (uint256 i; i < strs.length; i++) {
            if (i < strs.length - 1) a = abi.encodePacked(a, strs[i], bytes1(0));
            else a = abi.encodePacked(a, strs[i]);
        }
        return a;
    }


/**
 @notice Zap out in a single token
 @param _ToTokenContractAddress The ERC20 token to zapout in (address(0x00) if ether)
 @param _FromUniPoolAddress The uniswap pair address to zapout from
 @param _IncomingLP The amount of LP to remove.
 @param _minTokensRec indicates the minimum amount of tokens to receive
 @param _swapTarget indicates the execution target for swap.
 @param swap1Data DEX swap data
 @param swap2Data DEX swap data
 @param affiliate Affiliate address
 @return the amount of eth/tokens received after zapout
 */

function ZapOut(
        address _ToTokenContractAddress,
        address _FromUniPoolAddress,
        uint256 _IncomingLP,
        uint256 _minTokensRec,
        address[] memory _swapTarget,
        bytes memory swap1Data,
        bytes memory swap2Data,
        address affiliate
    ) public nonReentrant stopInEmergency returns (uint256 tokenBought) {
        //transfer goodwill and reoves liquidity
        (uint256 amountA, uint256 amountB) = _removeLiquidity(
            _FromUniPoolAddress,
            _IncomingLP
        );

        //swaps tokens to token
        tokenBought = _swapTokens(
            _FromUniPoolAddress,
            amountA,
            amountB,
            _ToTokenContractAddress,
            _swapTarget,
            swap1Data,
            swap2Data
        );
        require(tokenBought >= _minTokensRec, "High slippage");

        emit zapOut(
            msg.sender,
            _FromUniPoolAddress,
            _ToTokenContractAddress,
            tokenBought
        );

        uint256 totalGoodwillPortion;

        // transfer toTokens to sender
        if (_ToTokenContractAddress == address(0)) {
            totalGoodwillPortion = _subtractGoodwill(
                ETHAddress,
                tokenBought,
                affiliate
            );

            msg.sender.transfer(tokenBought.sub(totalGoodwillPortion));
        } else {
            totalGoodwillPortion = _subtractGoodwill(
                _ToTokenContractAddress,
                tokenBought,
                affiliate
            );

            IERC20(_ToTokenContractAddress).safeTransfer(
                msg.sender,
                tokenBought.sub(totalGoodwillPortion)
            );
        }

        return tokenBought.sub(totalGoodwillPortion);
    }


/**
 @notice Zap out in a pair of tokens with permit
 @param _FromUniPoolAddress indicates the liquidity pool
 @param _IncomingLP indicates the amount of LP to remove from pool
 @param affiliate Affiliate address to share fees
 @param _permitData indicates the encoded permit data, which contains owner, spender, value, deadline, v,r,s values.
 @return  amountA - indicates the amount received in token0, amountB - indicates the amount received in token1
 */

function ZapOut2PairTokenWithPermit(
        address _FromUniPoolAddress,
        uint256 _IncomingLP,
        address affiliate,
        bytes calldata _permitData
    ) external stopInEmergency returns (uint256 amountA, uint256 amountB) {
        // permit
        (bool success, ) = _FromUniPoolAddress.call(_permitData);
        require(success, "Could Not Permit");

        (amountA, amountB) = ZapOut2PairToken(
            _FromUniPoolAddress,
            _IncomingLP,
            affiliate
        );
    }


/**
 @notice Zap out in a signle token with permit
 @param _ToTokenContractAddress indicates the toToken address to which tokens to convert.
 @param _FromUniPoolAddress indicates the liquidity pool
 @param _IncomingLP indicates the amount of LP to remove from pool
 @param _minTokensRec indicatest the minimum amount of toTokens to receive
 @param _permitData indicates the encoded permit data, which contains owner, spender, value, deadline, v,r,s values.
 @param _swapTarget indicates the execution target for swap.
 @param swap1Data DEX swap data
 @param swap2Data DEX swap data
 @param affiliate Affiliate address to share fees
 */

function ZapOutWithPermit(
        address _ToTokenContractAddress,
        address _FromUniPoolAddress,
        uint256 _IncomingLP,
        uint256 _minTokensRec,
        bytes memory _permitData,
        address[] memory _swapTarget,
        bytes memory swap1Data,
        bytes memory swap2Data,
        address affiliate
    ) public stopInEmergency returns (uint256) {
        // permit
        (bool success, ) = _FromUniPoolAddress.call(_permitData);
        require(success, "Could Not Permit");

        return (
            ZapOut(
                _ToTokenContractAddress,
                _FromUniPoolAddress,
                _IncomingLP,
                _minTokensRec,
                _swapTarget,
                swap1Data,
                swap2Data,
                affiliate
            )
        );
    }


/**
 @notice this method returns the amount of tokens received in underlying tokens after removal of liquidity.
 @param _FromUniPoolAddress indicates the liquidity pool.
 @param _tokenA indicates the tokenA of pool
 @param _tokenB indicates the tokenB of pool
 @param _liquidity indicates the amount of liquidity to remove.
 @return  amountA - indicates the amount removed in token0, amountB - indicates the amount removed in token1
 */

function removeLiquidityReturn(
        address _FromUniPoolAddress,
        address _tokenA,
        address _tokenB,
        uint256 _liquidity
    ) external view returns (uint256 amountA, uint256 amountB) {
        IUniswapV2Pair pair = IUniswapV2Pair(_FromUniPoolAddress);

        (uint256 amount0, uint256 amount1) = _getBurnAmount(
            _FromUniPoolAddress,
            pair,
            _tokenA,
            _tokenB,
            _liquidity
        );

        (address token0, ) = _sortTokens(_tokenA, _tokenB);

        (amountA, amountB) = _tokenA == token0
            ? (amount0, amount1)
            : (amount1, amount0);

        require(amountA >= 1, "UniswapV2Router: INSUFFICIENT_A_AMOUNT");
        require(amountB >= 1, "UniswapV2Router: INSUFFICIENT_B_AMOUNT");
    }


/**
 * @dev given a wolf token supply, reserve token balance, reserve ratio, and a deposit amount (in the reserve token),
 * calculates the return for a given conversion (in the wolf token)
 *
 * Formula:
 * Return = _supply * ((1 + _depositAmount / _reserveBalance) ^ (_reserveRatio / MAX_RESERVE_RATIO) - 1)
 *
 * @param _supply              wolf token total supply
 * @param _reserveBalance    total reserve token balance
 * @param _reserveRatio     reserve ratio, represented in ppm, 1-1000000
 * @param _depositAmount       deposit amount, in reserve token
 *
 *  @return purchase return amount
 */

function calculatePurchaseReturn(
        uint256 _supply,
        uint256 _reserveBalance,
        uint32 _reserveRatio,
        uint256 _depositAmount) public view returns (uint256)
    {
        // validate input
        require(_supply > 0 && _reserveBalance > 0 && _reserveRatio > 0 && _reserveRatio <= MAX_RESERVE_RATIO, "Invalid inputs.");
        // special case for 0 deposit amount
        if (_depositAmount == 0) {
            return 0;
        }
        // special case if the ratio = 100%
        if (_reserveRatio == MAX_RESERVE_RATIO) {
            return _supply.mul(_depositAmount).div(_reserveBalance);
        }
        uint256 result;
        uint8 precision;
        uint256 baseN = _depositAmount.add(_reserveBalance);
        (result, precision) = power(
            baseN, _reserveBalance, _reserveRatio, MAX_RESERVE_RATIO
        );
        uint256 newTokenSupply = _supply.mul(result) >> precision;
        return newTokenSupply.sub(_supply);
    }


/**
 * @dev given a wolf token supply, reserve token balance, reserve ratio and a sell amount (in the wolf token),
 * calculates the return for a given conversion (in the reserve token)
 *
 * Formula:
 * Return = _reserveBalance * (1 - (1 - _sellAmount / _supply) ^ (1 / (_reserveRatio / MAX_RESERVE_RATIO)))
 *
 * @param _supply              wolf token total supply
 * @param _reserveBalance    total reserve token balance
 * @param _reserveRatio     constant reserve ratio, represented in ppm, 1-1000000
 * @param _sellAmount          sell amount, in the wolf token itself
 *
 * @return sale return amount
 */

function calculateSaleReturn(
        uint256 _supply,
        uint256 _reserveBalance,
        uint32 _reserveRatio,
        uint256 _sellAmount) public view returns (uint256)
    {
        // validate input
        require(_supply > 0 && _reserveBalance > 0 && _reserveRatio > 0 && _reserveRatio <= MAX_RESERVE_RATIO && _sellAmount <= _supply, "Invalid inputs.");
        // special case for 0 sell amount
        if (_sellAmount == 0) {
            return 0;
        }
        // special case for selling the entire supply
        if (_sellAmount == _supply) {
            return _reserveBalance;
        }
        // special case if the ratio = 100%
        if (_reserveRatio == MAX_RESERVE_RATIO) {
            return _reserveBalance.mul(_sellAmount).div(_supply);
        }
        uint256 result;
        uint8 precision;
        uint256 baseD = _supply.sub(_sellAmount);
        (result, precision) = power(
            _supply, baseD, MAX_RESERVE_RATIO, _reserveRatio
        );
        uint256 oldBalance = _reserveBalance.mul(result);
        uint256 newBalance = _reserveBalance << precision;
        return oldBalance.sub(newBalance).div(result);
    }


// Preminting 20 for marketing and community initatives

function _mintReservedTokens() internal onlyOwner {
      uint256 supply = totalSupply();
      uint256 _mintAmount = 20;
      require(supply == 0);

      for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(owner(), supply + i);
    }
  }


/* ------------- External ------------- */

function burnForBoost(IERC20 token) external payable {
        uint256 userData = _claimReward();

        uint256 boostCost = tokenBoostCosts[token];
        if (boostCost == 0) revert InvalidBoostToken();

        bool success = token.transferFrom(msg.sender, burnAddress, boostCost);
        if (!success) revert TransferFailed();

        uint256 boostStart = userData.boostStart();
        if (boostStart + TOKEN_BOOST_DURATION + TOKEN_BOOST_COOLDOWN > block.timestamp) revert BoostInEffect();

        _userData[msg.sender] = userData.setBoostStart(block.timestamp);

        emit BoostActivation(address(token));
    }


// calculates momentary totalBonus for display instead of effective bonus

function getUserStakeInfo(address user) external view returns (StakeInfo memory info) {
        unchecked {
            uint256 userData = _userData[user];

            info.numStaked = userData.numStaked();

            info.roleCount = userData.uniqueRoleCount();

            info.roleBonus = roleBonus(userData) / 100;
            info.specialGuestBonus = specialGuestBonus(user, userData) / 100;
            info.tokenBoost = (block.timestamp < userData.boostStart() + TOKEN_BOOST_DURATION) ? TOKEN_BONUS / 100 : 0;

            info.stakeStart = userData.stakeStart();
            info.timeBonus = (info.stakeStart > 0 &&
                block.timestamp > userData.stakeStart() + TIME_BONUS_STAKE_DURATION)
                ? TIME_BONUS / 100
                : 0;

            info.OGCount = userData.OGCount();
            info.OGBonus = (block.timestamp > OG_BONUS_END || userData.numStaked() == 0)
                ? 0
                : (userData.OGCount() * OG_BONUS) / userData.numStaked() / 100;

            info.rarityPoints = userData.rarityPoints();
            info.rarityBonus = rarityBonus(userData) / 100;

            info.totalBonus =
                info.roleBonus +
                info.specialGuestBonus +
                info.tokenBoost +
                info.timeBonus +
                info.rarityBonus +
                info.OGBonus;

            info.multiplierBase = userData.baseReward();
            info.dailyRewardBase = info.multiplierBase * dailyReward;

            info.dailyReward = (info.dailyRewardBase * (100 + info.totalBonus)) / 100;
            info.pendingReward = _pendingReward(user, userData);

            info.tokenBoostDelta = int256(TOKEN_BOOST_DURATION) - int256(block.timestamp - userData.boostStart());

            info.levelBalances = userData.levelBalances();
        }
    }


// O(N) read-only functions

function tokenIdsOf(address user, uint256 type_) external view returns (uint256[] memory) {
        unchecked {
            uint256 numTotal = type_ == 0 ? this.balanceOf(user) : type_ == 1
                ? this.numStaked(user)
                : this.numOwned(user);

            uint256[] memory ids = new uint256[](numTotal);

            if (numTotal == 0) return ids;

            uint256 count;
            for (uint256 i = startingIndex; i < totalSupply + startingIndex; ++i) {
                uint256 tokenData = _tokenDataOf(i);
                if (user == tokenData.trueOwner()) {
                    bool staked = tokenData.staked();
                    if ((type_ == 0 && !staked) || (type_ == 1 && staked) || type_ == 2) {
                        ids[count++] = i;
                        if (numTotal == count) return ids;
                    }
                }
            }

            return ids;
        }
    }


// low level token Pledge function

function procureTokens(address beneficiary) public payable {
    uint256 tokens;
    uint256 weiAmount = msg.value;
    uint256 backAmount;
    require(beneficiary != address(0));
    //minimum/maximum amount in ETH
    require(weiAmount >= minNumbPerSubscr && weiAmount <= maxNumbPerSubscr);
    if (now >= startICO && now <= endICO && totalICO < hardCap){
      tokens = weiAmount.mul(rate);
      if (hardCap.sub(totalICO) < tokens){
        tokens = hardCap.sub(totalICO);
        weiAmount = tokens.div(rate);
        backAmount = msg.value.sub(weiAmount);
      }
      totalICO = totalICO.add(tokens);
    }

    require(tokens > 0);
    token.mint(beneficiary, tokens);
    balancesSoftCap[beneficiary] = balancesSoftCap[beneficiary].add(weiAmount);

    uint256 dateEndRefund = now + 14 * 1 days;
    paymentCounter[beneficiary] = paymentCounter[beneficiary] + 1;
    payments[beneficiary][paymentCounter[beneficiary]] = BuyInfo(weiAmount, tokens, dateEndRefund);

    if (backAmount > 0){
      msg.sender.transfer(backAmount);
    }
    emit TokenProcurement(msg.sender, beneficiary, weiAmount, tokens);
  }


// trait counts are set through hook in madmouse contract (MadMouse::_beforeStakeDataTransform)

function uniqueRoleCount(uint256 userData) internal pure returns (uint256) {
        unchecked {
            return (toUInt256((userData >> (128)) & 0xFF > 0) +
                toUInt256((userData >> (128 + 8)) & 0xFF > 0) +
                toUInt256((userData >> (128 + 16)) & 0xFF > 0) +
                toUInt256((userData >> (128 + 24)) & 0xFF > 0) +
                toUInt256((userData >> (128 + 32)) & 0xFF > 0));
        }
    }