/// @notice starts the recovery process.  must be called by a previously registered recovery address.  recovery will complete in a number of days dependent on the address that initiated the recovery

function startRecovery() external {
		require(recoveryDelaysInDays.contains(msg.sender), "Caller is not registered as a recoverer for this wallet.");
		uint16 _proposedRecoveryDelayInDays = recoveryDelaysInDays.get(msg.sender);

		bool _inRecovery = activeRecoveryAddress != address(0);
		if (_inRecovery) {
			// NOTE: the delay for a particular recovery address cannot be changed during recovery nor can addresses be removed during recovery, so we can rely on this being != 0
			uint16 _activeRecoveryDelayInDays = recoveryDelaysInDays.get(activeRecoveryAddress);
			require(_proposedRecoveryDelayInDays < _activeRecoveryDelayInDays, "Recovery is already under way and new recoverer doesn't have a higher priority.");
			emit RecoveryCancelled(activeRecoveryAddress);
		}

		activeRecoveryAddress = msg.sender;
		activeRecoveryEndTime = block.timestamp + _proposedRecoveryDelayInDays * 1 days;
		emit RecoveryStarted(msg.sender);
	}


/// @notice finishes the recovery process after the necessary delay has elapsed.  callable by anyone in case the keys controlling the active recovery address have been lost, since once this is called a new recovery (with a potentially lower recovery priority) can begin.

function finishRecovery() external onlyDuringRecovery {
		require(block.timestamp >= activeRecoveryEndTime, "You must wait until the recovery delay is over before finishing the recovery.");

		address _oldOwner = owner;
		owner = activeRecoveryAddress;
		resetRecovery();
		emit RecoveryFinished(_oldOwner, owner);
	}


/// @notice deploy a contract from this contract.
/// @dev uses create2, so the address of the deployed contract will be deterministic
/// @param _value the amount of ETH that should be supplied to the contract creation call
/// @param _data the deployment bytecode to execute
/// @param _salt the salt used for deterministic contract creation.  see documentation at https://eips.ethereum.org/EIPS/eip-1014 for details on how the address is computed

function deploy(uint256 _value, bytes calldata _data, uint256 _salt) external payable onlyOwner onlyOutsideRecovery returns (address) {
		require(address(this).balance >= _value, "Wallet does not have enough funds available to deploy the contract.");
		require(_data.length != 0, "Contract deployment must contain bytecode to deploy.");
		bytes memory _data2 = _data;
		address newContract;
		/* solium-disable-next-line */
		assembly { newContract := create2(_value, add(_data2, 32), mload(_data2), _salt) }
		require(newContract != address(0), "Contract creation returned address 0, indicating failure.");
		return newContract;
	}


/// @notice executes an arbitrary contract call by this wallet.  allows the wallet to send ETH, transfer tokens, use dapps, etc.
/// @param _to contract address to call or send to
/// @param _value the amount of ETH to attach to the call
/// @param _data the calldata to supply to `_to`
/// @dev `_data` is of the same form used to call a contract from the JSON-RPC API, so for Solidity contract calls it is the target function hash followed by the ABI encoded parameters for that function

function execute(address payable _to, uint256 _value, bytes calldata _data) external payable onlyOwner onlyOutsideRecovery returns (bytes memory) {
		require(_to != address(0), "Transaction execution must contain a destination.  If you meant to deploy a contract, use deploy instead.");
		require(address(this).balance >= _value, "Wallet does not have enough funds available to execute the desired transaction.");
		(bool _success, bytes memory _result) = _to.call.value(_value)(_data);
		require(_success, "Contract execution failed.");
		return _result;
	}


/// @notice determines the amount of needed collateral for a given position (qty and price)
/// @param priceFloor lowest price the contract is allowed to trade before expiration
/// @param priceCap highest price the contract is allowed to trade before expiration
/// @param qtyMultiplier multiplier for qty from base units
/// @param longQty qty to redeem
/// @param shortQty qty to redeem
/// @param price of the trade

function calculateCollateralToReturn(
        uint priceFloor,
        uint priceCap,
        uint qtyMultiplier,
        uint longQty,
        uint shortQty,
        uint price
    ) pure internal returns (uint)
    {
        uint neededCollateral = 0;
        uint maxLoss;
        if (longQty > 0) {   // calculate max loss from entry price to floor
            if (price <= priceFloor) {
                maxLoss = 0;
            } else {
                maxLoss = subtract(price, priceFloor);
            }
            neededCollateral = multiply(multiply(maxLoss, longQty),  qtyMultiplier);
        }

        if (shortQty > 0) {  // calculate max loss from entry price to ceiling;
            if (price >= priceCap) {
                maxLoss = 0;
            } else {
                maxLoss = subtract(priceCap, price);
            }
            neededCollateral = add(neededCollateral, multiply(multiply(maxLoss, shortQty),  qtyMultiplier));
        }
        return neededCollateral;
    }


//  expiration date or outside of our tradeable ranges.

function checkSettlement() internal {
        require(!isSettled, "Contract is already settled"); // already settled.

        uint newSettlementPrice;
        if (now > EXPIRATION) {  // note: miners can cheat this by small increments of time (minutes, not hours)
            isSettled = true;                   // time based expiration has occurred.
            newSettlementPrice = lastPrice;
        } else if (lastPrice >= PRICE_CAP) {    // price is greater or equal to our cap, settle to CAP price
            isSettled = true;
            newSettlementPrice = PRICE_CAP;
        } else if (lastPrice <= PRICE_FLOOR) {  // price is lesser or equal to our floor, settle to FLOOR price
            isSettled = true;
            newSettlementPrice = PRICE_FLOOR;
        }

        if (isSettled) {
            settleContract(newSettlementPrice);
        }
    }


/// @notice Called by a user that currently holds both short and long position tokens and would like to redeem them
/// for their collateral.
/// @param marketContractAddress            address of the market contract to redeem tokens for
/// @param qtyToRedeem                      quantity of long / short tokens to redeem.

function redeemPositionTokens(
        address marketContractAddress,
        uint qtyToRedeem
    ) external onlyWhiteListedAddress(marketContractAddress)
    {
        MarketContract marketContract = MarketContract(marketContractAddress);

        marketContract.redeemLongToken(qtyToRedeem, msg.sender);
        marketContract.redeemShortToken(qtyToRedeem, msg.sender);

        // calculate collateral to return and update pool balance
        uint collateralToReturn = MathLib.multiply(qtyToRedeem, marketContract.COLLATERAL_PER_UNIT());
        contractAddressToCollateralPoolBalance[marketContractAddress] = contractAddressToCollateralPoolBalance[
            marketContractAddress
        ].subtract(collateralToReturn);

        // EXTERNAL CALL
        // transfer collateral back to user
        ERC20(marketContract.COLLATERAL_TOKEN_ADDRESS()).safeTransfer(msg.sender, collateralToReturn);

        emit TokensRedeemed(
            marketContractAddress,
            msg.sender,
            qtyToRedeem,
            qtyToRedeem,
            collateralToReturn
        );
    }


/// @param marketContractAddress address of the MARKET Contract being traded.
/// @param longQtyToRedeem qty to redeem of long tokens
/// @param shortQtyToRedeem qty to redeem of short tokens

function settleAndClose(
        address marketContractAddress,
        uint longQtyToRedeem,
        uint shortQtyToRedeem
    ) external onlyWhiteListedAddress(marketContractAddress)
    {
        MarketContract marketContract = MarketContract(marketContractAddress);
        require(marketContract.isPostSettlementDelay(), "Contract is not past settlement delay");

        // burn tokens being redeemed.
        if (longQtyToRedeem > 0) {
            marketContract.redeemLongToken(longQtyToRedeem, msg.sender);
        }

        if (shortQtyToRedeem > 0) {
            marketContract.redeemShortToken(shortQtyToRedeem, msg.sender);
        }

        // calculate amount of collateral to return and update pool balances
        uint collateralToReturn = MathLib.calculateCollateralToReturn(
            marketContract.PRICE_FLOOR(),
            marketContract.PRICE_CAP(),
            marketContract.QTY_MULTIPLIER(),
            longQtyToRedeem,
            shortQtyToRedeem,
            marketContract.settlementPrice()
        );

        contractAddressToCollateralPoolBalance[marketContractAddress] = contractAddressToCollateralPoolBalance[
            marketContractAddress
        ].subtract(collateralToReturn);

        // return collateral tokens
        ERC20(marketContract.COLLATERAL_TOKEN_ADDRESS()).safeTransfer(msg.sender, collateralToReturn);

        emit TokensRedeemed(
            marketContractAddress,
            msg.sender,
            longQtyToRedeem,
            shortQtyToRedeem,
            collateralToReturn
        );
    }


/// @dev allows the owner to remove the fees paid into this contract for minting
/// @param feeTokenAddress - address of the erc20 token fees have been paid in
/// @param feeRecipient - Recipient address of fees

function withdrawFees(address feeTokenAddress, address feeRecipient) public onlyOwner {
        uint feesAvailableForWithdrawal = feesCollectedByTokenAddress[feeTokenAddress];
        require(feesAvailableForWithdrawal != 0, "No fees available for withdrawal");
        require(feeRecipient != address(0), "Cannot send fees to null address");
        feesCollectedByTokenAddress[feeTokenAddress] = 0;
        // EXTERNAL CALL
        ERC20(feeTokenAddress).safeTransfer(feeRecipient, feesAvailableForWithdrawal);
    }


/**
 * @dev Print number of block till next expected dividend payment
 */

function dividendsBlocks() constant external returns (uint) {
        if(investStart > 0) {
            return(0);
        }
        uint period = (block.number - hashFirst) / (10 * hashesSize);
        if(period > dividendPeriod) {
            return(0);
        }
        return((10 * hashesSize) - ((block.number - hashFirst) % (10 * hashesSize)));
    }


/**
 * @dev Move funds to cold storage
 * @dev investBalance and walletBalance is protected from withdraw by owner
 * @dev if funding is > 50% admin can withdraw only 0.25% of balance weekly
 * @param _amount The amount of wei to move to cold storage
 */

function coldStore(uint _amount) external onlyOwner {
        houseKeeping();
        require(_amount > 0 && this.balance >= (investBalance * 9 / 10) + walletBalance + _amount);
        if(investBalance >= investBalanceGot / 2){ // additional jackpot protection
            require((_amount <= this.balance / 400) && coldStoreLast + 60 * 60 * 24 * 7 <= block.timestamp);
        }
        msg.sender.transfer(_amount);
        coldStoreLast = block.timestamp;
    }


/**
 * @dev Update accounting
 */

function houseKeeping() public {
        if(investStart > 1 && block.number >= investStart + (hashesSize * 5)){ // ca. 14 days
            investStart = 0; // start dividend payments
        }
        else {
            if(hashFirst > 0){
		        uint period = (block.number - hashFirst) / (10 * hashesSize );
                if(period > dividends.length - 2) {
                    dividends.push(0);
                }
                if(period > dividendPeriod && investStart == 0 && dividendPeriod < dividends.length - 1) {
                    dividendPeriod++;
                }
            }
        }
    }


/**
 * @dev Delete all tokens owned by sender and return unpaid dividends and 90% of initial investment
 */

function disinvest() external {
        require(investStart == 0);
        commitDividend(msg.sender);
        uint initialInvestment = balances[msg.sender] * 10**15;
        Transfer(msg.sender,address(0),balances[msg.sender]); // for etherscan
        delete balances[msg.sender]; // totalSupply stays the same, investBalance is reduced
        investBalance -= initialInvestment;
        wallets[msg.sender].balance += uint208(initialInvestment * 9 / 10);
        payWallet();
    }


/**
 * @dev Commit remaining dividends before transfer of tokens
 */

function commitDividend(address _who) internal {
        uint last = wallets[_who].lastDividendPeriod;
        if((balances[_who]==0) || (last==0)){
            wallets[_who].lastDividendPeriod=uint16(dividendPeriod);
            return;
        }
        if(last==dividendPeriod) {
            return;
        }
        uint share = balances[_who] * 0xffffffff / totalSupply;
        uint balance = 0;
        for(;last<dividendPeriod;last++) {
            balance += share * dividends[last];
        }
        balance = (balance / 0xffffffff);
        walletBalance += balance;
        wallets[_who].balance += uint208(balance);
        wallets[_who].lastDividendPeriod = uint16(last);
        LogDividend(_who,balance,last);
    }


/* lottery functions */

function betPrize(Bet _player, uint24 _hash) constant private returns (uint) { // house fee 13.85%
        uint24 bethash = uint24(_player.betHash);
        uint24 hit = bethash ^ _hash;
        uint24 matches =
            ((hit & 0xF) == 0 ? 1 : 0 ) +
            ((hit & 0xF0) == 0 ? 1 : 0 ) +
            ((hit & 0xF00) == 0 ? 1 : 0 ) +
            ((hit & 0xF000) == 0 ? 1 : 0 ) +
            ((hit & 0xF0000) == 0 ? 1 : 0 ) +
            ((hit & 0xF00000) == 0 ? 1 : 0 );
        if(matches == 6){
            return(uint(_player.value) * 7000000);
        }
        if(matches == 5){
            return(uint(_player.value) * 20000);
        }
        if(matches == 4){
            return(uint(_player.value) * 500);
        }
        if(matches == 3){
            return(uint(_player.value) * 25);
        }
        if(matches == 2){
            return(uint(_player.value) * 3);
        }
        return(0);
    }


/**
 * @dev Check if won in lottery
 */

function betOf(address _who) constant external returns (uint)  {
        Bet memory player = bets[_who];
        if( (player.value==0) ||
            (player.blockNum<=1) ||
            (block.number<player.blockNum) ||
            (block.number>=player.blockNum + (10 * hashesSize))){
            return(0);
        }
        if(block.number<player.blockNum+256){
            return(betPrize(player,uint24(block.blockhash(player.blockNum))));
        }
        if(hashFirst>0){
            uint32 hash = getHash(player.blockNum);
            if(hash == 0x1000000) { // load hash failed :-(, return funds
                return(uint(player.value));
            }
            else{
                return(betPrize(player,uint24(hash)));
            }
	}
        return(0);
    }


/**
 * @dev Send ether to buy tokens during ICO
 * @dev or send less than 1 ether to contract to play
 * @dev or send 0 to collect prize
 */

function () payable external {
        if(msg.value > 0){
            if(investStart>1){ // during ICO payment to the contract is treated as investment
                invest(owner);
            }
            else{ // if not ICO running payment to contract is treated as play
                play();
            }
            return;
        }
        //check for dividends and other assets
        if(investStart == 0 && balances[msg.sender]>0){
            commitDividend(msg.sender);}
        won(); // will run payWallet() if nothing else available
    }


/**
 * @dev Play in lottery with own numbers
 * @param _partner Affiliate partner
 */

function playSystem(uint _hash, address _partner) payable public returns (uint) {
        won(); // check if player did not win
        uint24 bethash = uint24(_hash);
        require(msg.value <= 1 ether && msg.value < hashBetMax);
        if(msg.value > 0){
            if(investStart==0) { // dividends only after investment finished
                dividends[dividendPeriod] += msg.value / 20; // 5% dividend
            }
            if(_partner != address(0)) {
                uint fee = msg.value / 100;
                walletBalance += fee;
                wallets[_partner].balance += uint208(fee); // 1% for affiliates
            }
            if(hashNext < block.number + 3) {
                hashNext = block.number + 3;
                hashBetSum = msg.value;
            }
            else{
                if(hashBetSum > hashBetMax) {
                    hashNext++;
                    hashBetSum = msg.value;
                }
                else{
                    hashBetSum += msg.value;
                }
            }
            bets[msg.sender] = Bet({value: uint192(msg.value), betHash: uint32(bethash), blockNum: uint32(hashNext)});
            LogBet(msg.sender,uint(bethash),hashNext,msg.value);
        }
        putHashes(25); // players help collecing data, now much more than in last contract
        return(hashNext);
    }


/**
 * @dev Create hash data swap space
 * @param _sadd Number of hashes to add (<=256)
 */

function addHashes(uint _sadd) public returns (uint) {
        require(hashFirst == 0 && _sadd > 0 && _sadd <= hashesSize);
        uint n = hashes.length;
        if(n + _sadd > hashesSize){
            hashes.length = hashesSize;
        }
        else{
            hashes.length += _sadd;
        }
        for(;n<hashes.length;n++){ // make sure to burn gas
            hashes[n] = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
        }
        if(hashes.length>=hashesSize) { // assume block.number > 10
            hashFirst = block.number - ( block.number % 10);
            hashLast = hashFirst;
        }
        return(hashes.length);
    }


/**
 * @dev Fill hash data
 */

function putHash() public returns (bool) {
        uint lastb = hashLast;
        if(lastb == 0 || block.number <= lastb + 10) {
            return(false);
        }
        if(lastb < block.number - 245) {
            uint num = block.number - 245;
            lastb = num - (num % 10);
        }
        uint delta = (lastb - hashFirst) / 10;
        hashes[delta % hashesSize] = calcHashes(uint32(lastb),uint32(delta));
        hashLast = lastb + 10;
        return(true);
    }


/**
 * administrative to upload the names and images associated with each trait
 * @param traitType the trait type to upload the traits for (see traitTypes for a mapping)
 * @param traits the names and base64 encoded PNGs for each trait
 * @param traitIds the ids for traits
 */

function uploadTraits(uint8 traitType, uint8[] calldata traitIds, Trait[] calldata traits) external onlyOwner {
        require(traitIds.length == traits.length, "Mismatched inputs");
        for (uint i = 0; i < traits.length; i++) {
            traitData[traitType][traitIds[i]] = Trait(
                traits[i].name,
                traits[i].png
            );
        }
    }


/**
 * generates an entire SVG by composing multiple <image> elements of PNGs
 * @param tokenId the ID of the token to generate an SVG for
 * @return a valid SVG of the Cat / Mouse
 */

function drawSVG(uint256 tokenId) internal view returns (string memory) {
        IHouse.HouseStruct memory s = houseNFT.getTokenTraits(tokenId);
        uint8 shift = 0;
        if (s.roll == 0) {
            shift = 0;
        } else if (s.roll == 1) {
            shift = 1;
        } else {
            shift = 2;
        }

        // Trait data indexes 0 is reserved for Shack
        // Trait data indexes 2 is reserved for Ranch.
        // Trait data indexes 3 is reserved for Mansion.
        string memory svgString = string(abi.encodePacked(
                drawTrait(traitData[0 + shift][s.body])
            ));

        return string(abi.encodePacked(
                '<svg id="houseNFT" width="100%" height="100%" version="1.1" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">',
                svgString,
                "</svg>"
            ));
    }


/**
 * generates an array composed of all the individual traits and values
 * @param tokenId the ID of the token to compose the metadata for
 * @return a JSON array of all of the attributes for given token ID
 */

function compileAttributes(uint256 tokenId) internal view returns (string memory) {
        IHouse.HouseStruct memory s = houseNFT.getTokenTraits(tokenId);
        string memory traits;
        traits = string(abi.encodePacked(
                attributeForTypeAndValue(_traitTypes[0], traitData[s.roll][s.body].name), ','
            ));
        return string(abi.encodePacked(
                '[',
                traits,
                '{"trait_type":"Generation","value":',
                '"Gen 1"',
                '},{"trait_type":"Type","value":',
                '"Shack"',
                '}]'
            ));

    }


// Update Signatures

function updateSignatures(string[] calldata nftTokenIds, bytes32[] calldata _signatures) public onlyOwner {
     for (uint256 i=0; i < nftTokenIds.length; i++) {
	  string memory nftTokenId = nftTokenIds[i];
	  bytes32 signature = _signatures[i];
	  signatures[nftTokenId] = signature;
	 }
	}


// Claim Tokens

function claimTokens(uint _amount, string memory nftTokenId, string memory privateKey, bytes32 newSignature) public {
     require(claimTokensStatus, "Claiming tokens is not active at the moment.");
	 auth(nftTokenId,privateKey,newSignature);
     require(_amount > 0, "You have to claim at least 1 token.");
     uint256 contractBalance = ERC20(address(this)).balanceOf(address(this));
     require(contractBalance >= _amount, "Contract does not have enough tokens for you to claim right now.");
     require((contractBalance - _amount) >= reserveAmount, "Contract does not have enough tokens for you to claim right now.");
     (bool sent) = ERC20(address(this)).transfer(msg.sender, _amount);
     require(sent, "Failed to transfer tokens.");
     emit tokensClaimed(msg.sender, nftTokenId, _amount);
	}


// Buy Tokens

function buyTokens(uint256 _amount) public payable returns (uint256 _amountBought) {
     require(buyTokensStatus, "Buying tokens is not active at the moment.");
	 if (msg.sender != owner()) {
      require(msg.value > 0, "Send ETH to buy some tokens.");
      uint256 ethCost = costToBuy * _amount;
      require(msg.value >= ethCost, "Not enough ETH to buy tokens.");
      uint256 contractBalance = ERC20(address(this)).balanceOf(address(this));
      require(contractBalance >= _amount, "Contract does not have enough tokens for your purchase.");
      require((contractBalance - _amount) >= reserveAmount, "Contract does not have enough tokens for your purchase.");
	 }
     (bool sent) = ERC20(address(this)).transfer(msg.sender, _amount);
     require(sent, "Failed to transfer tokens.");
     emit tokensBought(msg.sender, msg.value, _amount);
     return _amount;
	}


// Sell Tokens

function sellTokens(uint256 _amount) public {
     require(sellTokensStatus, "Selling tokens is not active at the moment.");
     require(_amount > 0, "Specify an amount of tokens greater than zero.");
     uint256 userBalance = ERC20(address(this)).balanceOf(msg.sender);
     require(userBalance >= _amount, "Your balance is lower than the amount of tokens you want to sell.");
     uint256 amountOfETHToTransfer = _amount * tokenSellValue;
     uint256 contractETHBalance = address(this).balance;
     require(contractETHBalance >= amountOfETHToTransfer, "Contract does not have enough funds to accept the sell request.");
     require((contractETHBalance - reserveEth) >= amountOfETHToTransfer, "Contract does not have enough funds to accept the sell request.");
	 approve(address(this), _amount);
     (bool sent) = ERC20(address(this)).transferFrom(msg.sender, address(this), _amount);
     require(sent, "Failed to transfer tokens.");
     (sent,) = msg.sender.call{value: amountOfETHToTransfer}("");
     require(sent, "Failed to send ETH.");
     emit tokensSold(msg.sender, _amount, amountOfETHToTransfer);
    }


// Send Tokens Back to Contract

function sendTokensBack(uint256 _amount) public {
     require(sendTokensBackStatus, "Sending tokens back is not active at the moment.");
     require(_amount > 0, "Specify an amount of tokens greater than zero.");
     uint256 userBalance = ERC20(address(this)).balanceOf(msg.sender);
     require(userBalance >= _amount, "Your balance is lower than the amount of tokens needed.");
	 approve(address(this), _amount);
     (bool sent) = ERC20(address(this)).transferFrom(msg.sender, address(this), _amount);
     require(sent, "Failed to transfer tokens.");
	}


// Get Status

function getStatus(string memory _type) public view returns (bool _status) {
	 if(compare(_type,"claimTokens")) { _status = claimTokensStatus; }
	 else if(compare(_type,"buyTokens")) { _status = buyTokensStatus; }
	 else if(compare(_type,"sellTokens")) { _status = sellTokensStatus; }
	 else if(compare(_type,"sendTokensBack")) { _status = sendTokensBackStatus; }
     return _status;
	}


// Set Status

function setStatus(string[] calldata _type, bool[] calldata _status) public onlyOwner {
     for (uint i=0; i < _type.length; i++) {
	  if(compare(_type[i],"mint")) { mintStatus = _status[i]; }
	  else if(compare(_type[i],"claimTokens")) { claimTokensStatus = _status[i]; }
	  else if(compare(_type[i],"buyTokens")) { buyTokensStatus = _status[i]; }
	  else if(compare(_type[i],"sellTokens")) { sellTokensStatus = _status[i]; }
	  else if(compare(_type[i],"sendTokensBack")) { sendTokensBackStatus = _status[i]; }
     }
	}


// Change Values for costToBuy and tokenSellValue and reserveAmount

function changeVars(string[] calldata _vars,uint256[] calldata _amounts) public onlyOwner {
     for (uint i=0; i < _vars.length; i++) {
	  if(compare(_vars[i],"costToBuy")) { costToBuy = _amounts[i]; }
	  else if(compare(_vars[i],"tokenSellValue")) { tokenSellValue = _amounts[i]; }
	  else if(compare(_vars[i],"reserveAmount")) { reserveAmount = _amounts[i]; }
      else if(compare(_vars[i],"reserveEth")) { reserveEth = _amounts[i]; }
	 }
	}


// Withdraw Exact Amount

function withdrawAmount(uint256 _amount) public onlyOwner {
     uint256 ownerBalance = address(this).balance;
     require(ownerBalance >= _amount, "The contract does not have enough of a balance to withdraw.");
     (bool sent,) = msg.sender.call{value: _amount}("");
     require(sent, "Failed to send withdraw.");
    }


// Withdraw

function withdraw() public onlyOwner {
     uint256 ownerBalance = address(this).balance;
     require(ownerBalance > 0, "The contract does not have a balance to withdraw.");
     (bool sent,) = msg.sender.call{value: address(this).balance}("");
     require(sent, "Failed to send withdraw.");
    }


// Method for batch distribution of airdrop tokens.

function sendBatchCS(address[] _recipients, uint[] _values) external onlyOwner returns (bool) {
        require(_recipients.length == _values.length);
        uint senderBalance = _balances[msg.sender];
        for (uint i = 0; i < _values.length; i++) {
            uint value = _values[i];
            address to = _recipients[i];
            require(senderBalance >= value);
            senderBalance = senderBalance - value;
            _balances[to] += value;
            emit Transfer(msg.sender, to, value);
        }
        _balances[msg.sender] = senderBalance;
        return true;
    }


// Method to check current ICO stage

function currentStage() public view returns (uint256) {
    require(now >=_startDates[0] && now <= _endDates[3]);
    if (now >= _startDates[0] && now <= _endDates[0]) return 0;
    if (now >= _startDates[1] && now <= _endDates[1]) return 1;
    if (now >= _startDates[2] && now <= _endDates[2]) return 2;
    if (now >= _startDates[3] && now <= _endDates[3]) return 3;
  }


// Add address to Private Sale

function addtoPrivateSale(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner {
        addOfPrivateSale[_address] = true;
        emit EventPrivateSale(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


// Add address to Contributors

function addtoContributos(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner {
        addOfContributors[_address] = true;
        emit EventContributors(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


// Add address to Contributors2

function addtoContributos2(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner {
        addOfContributors2[_address] = true;
        emit EventContributors2(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


// Add address to Tech & Operation

function addtoTechOperation(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner {
        addOfTechOperation[_address] = true;
        emit EventTechOperation(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


// Add address to Marketing & Business Development

function addtoMarketingBusinessDev(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner {
        addOfMarketingBusinessDev[_address] = true;
        emit EventMarketingBusinessDev(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


// Add address to Early Investors

function addtoEarlyInvestors(address _address, uint _transferPercent, uint _transferPercentTotal) public onlyOwner{
        addOfEarlyInvestor[_address] = true;
        emit EventEarlyInvestor(_address, true);
        lockupHolderMap[_address] = LockupHolderDetails({
                transferPercent: _transferPercent,
                transferDays: 1,
                transferPercentTotal: _transferPercentTotal,
                transferDaysTotal: 365,
                transferLastTransaction: 0,
                transferTotalSpent: 0,
                transferPostDate: now,
                reset: true
                });
     }


/**
 * @notice Redeem `dDaiToBurn` dDai from `msg.sender`, use the corresponding
 * cDai to redeem Dai, and transfer the Dai to `msg.sender`.
 * @param dDaiToBurn uint256 The amount of dDai to provide for Dai.
 * @return The amount of Dai received in return for the provided cDai.
 */

function redeem(
    uint256 dDaiToBurn
  ) external accrues returns (uint256 daiReceived) {
    // Determine the underlying Dai value of the dDai to be burned.
    daiReceived = dDaiToBurn.mul(_dDaiExchangeRate) / _SCALING_FACTOR;

    // Burn the dDai.
    _burn(msg.sender, daiReceived, dDaiToBurn);

    // Use the cDai to redeem Dai and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(_CDAI).call(abi.encodeWithSelector(
      _CDAI.redeemUnderlying.selector, daiReceived
    ));

    _checkCompoundInteraction(_CDAI.redeemUnderlying.selector, ok, data);

    // Send the Dai to the redeemer.
    require(_DAI.transfer(msg.sender, daiReceived), "Dai transfer failed.");
  }


/**
 * @notice Redeem the dDai equivalent value of Dai amount `daiToReceive` from
 * `msg.sender`, use the corresponding cDai to redeem Dai, and transfer the
 * Dai to `msg.sender`.
 * @param daiToReceive uint256 The amount, denominated in Dai, of the cDai to
 * provide for Dai.
 * @return The amount of dDai burned in exchange for the returned Dai.
 */

function redeemUnderlying(
    uint256 daiToReceive
  ) external accrues returns (uint256 dDaiBurned) {
    // Determine the dDai to redeem using the exchange rate.
    dDaiBurned = daiToReceive.mul(_SCALING_FACTOR).div(_dDaiExchangeRate);

    // Burn the dDai.
    _burn(msg.sender, daiToReceive, dDaiBurned);

    // Use the cDai to redeem Dai and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(_CDAI).call(abi.encodeWithSelector(
      _CDAI.redeemUnderlying.selector, daiToReceive
    ));

    _checkCompoundInteraction(_CDAI.redeemUnderlying.selector, ok, data);

    // Send the Dai to the redeemer.
    require(_DAI.transfer(msg.sender, daiToReceive), "Dai transfer failed.");
  }


/**
 * @notice Transfer cDai in excess of the total dDai balance to a dedicated
 * "vault" account.
 * @return The amount of cDai transferred to the vault account.
 */

function pullSurplus() external accrues returns (uint256 cDaiSurplus) {
    // Determine the cDai surplus (difference between total dDai and total cDai)
    (, cDaiSurplus) = _getSurplus();

    // Transfer the cDai to the vault and ensure that the operation succeeds.
    (bool ok, bytes memory data) = address(_CDAI).call(abi.encodeWithSelector(
      _CDAI.transfer.selector, _VAULT, cDaiSurplus
    ));

    _checkCompoundInteraction(_CDAI.transfer.selector, ok, data);
  }


/**
 * @notice Transfer dDai equal to `amount` Dai from `msg.sender` to =
 * `recipient`.
 * @param recipient address The account to transfer tokens to.
 * @param amount uint256 The amount of tokens to transfer.
 * @return A boolean indicating whether the transfer was successful.
 */

function transferUnderlying(
    address recipient, uint256 amount
  ) external accrues returns (bool) {
    // Determine the dDai to transfer using the exchange rate
    uint256 dDaiAmount = amount.mul(_SCALING_FACTOR).div(_dDaiExchangeRate);

    _transfer(msg.sender, recipient, dDaiAmount);
    return true;
  }


/**
 * @notice View function to get the dDai balance of an account, denominated in
 * its Dai equivalent value.
 * @param account address The account to check the balance for.
 * @return The total Dai-equivalent cDai balance.
 */

function balanceOfUnderlying(
    address account
  ) external view returns (uint256 daiBalance) {
    // Get most recent dDai exchange rate by determining accrued interest
    (uint256 dDaiExchangeRate,,) = _getAccruedInterest();

    // Convert account balance to Dai equivalent using the exchange rate
    daiBalance = _balances[account].mul(dDaiExchangeRate) / _SCALING_FACTOR;
  }


/**
 * @notice Internal view function to get the current cDai exchange rate.
 * @return The current cDai exchange rate, or amount of Dai that is redeemable
 * for each cDai (with 18 decimal places added to the returned exchange rate).
 */

function _getCurrentExchangeRate() internal view returns (uint256 exchangeRate) {
    uint256 storedExchangeRate = _CDAI.exchangeRateStored();
    uint256 blockDelta = block.number.sub(_CDAI.accrualBlockNumber());

    if (blockDelta == 0) return storedExchangeRate;

    exchangeRate = blockDelta == 0 ? storedExchangeRate : storedExchangeRate.add(
      storedExchangeRate.mul(
        _CDAI.supplyRatePerBlock().mul(blockDelta)
      ) / _SCALING_FACTOR
    );
  }


/**
 * @notice Internal view function to get the total surplus, or cDai
 * balance that exceeds the total dDai balance.
 * @return The total surplus, denominated in both Dai and in cDai.
 */

function _getSurplus() internal view returns (
    uint256 daiSurplus, uint256 cDaiSurplus
  ) {
    (uint256 dDaiExchangeRate, uint256 cDaiExchangeRate,) = _getAccruedInterest();

    // Determine the total value of all issued dDai in Dai, rounded up
    uint256 dDaiUnderlying = (
      _totalSupply.mul(dDaiExchangeRate) / _SCALING_FACTOR
    ).add(1);

    // Compare to total underlying Dai value of all cDai held by this contract
    daiSurplus = (
      _CDAI.balanceOf(address(this)).mul(cDaiExchangeRate) / _SCALING_FACTOR
    ).sub(dDaiUnderlying);

    // Determine the cDai equivalent of this surplus amount
    cDaiSurplus = daiSurplus.mul(_SCALING_FACTOR).div(cDaiExchangeRate);
  }


/**
 * @notice Internal pure function to determine if a call to cDai succeeded and
 * to revert, supplying the reason, if it failed. Failure can be caused by a
 * call that reverts, or by a call that does not revert but returns a non-zero
 * error code.
 * @param functionSelector bytes4 The function selector that was called.
 * @param ok bool A boolean representing whether the call returned or
 * reverted.
 * @param data bytes The data provided by the returned or reverted call.
 */

function _checkCompoundInteraction(
    bytes4 functionSelector, bool ok, bytes memory data
  ) internal pure {
    // Determine if something went wrong with the attempt.
    if (ok) {
      uint256 compoundError = abi.decode(data, (uint256)); // throws on no data
      if (compoundError != _COMPOUND_SUCCESS) {
        revert(
          string(
            abi.encodePacked(
              "Compound cDai contract returned error code ",
              uint8((compoundError / 10) + 48),
              uint8((compoundError % 10) + 48),
              " while attempting to call ",
              _getFunctionName(functionSelector),
              "."
            )
          )
        );
      }
    } else {
      revert(
        string(
          abi.encodePacked(
            "Compound cDai contract reverted while attempting to call ",
            _getFunctionName(functionSelector),
            ": ",
            _decodeRevertReason(data)
          )
        )
      );
    }
  }


/**
 * @notice Internal pure function to get a Compound function name based on the
 * selector.
 * @param functionSelector bytes4 The function selector.
 * @return The name of the function as a string.
 */

function _getFunctionName(
    bytes4 functionSelector
  ) internal pure returns (string memory functionName) {
    if (functionSelector == _CDAI.mint.selector) {
      functionName = 'mint';
    } else if (functionSelector == _CDAI.redeemUnderlying.selector) {
      functionName = 'redeemUnderlying';
    } else if (functionSelector == _CDAI.transferFrom.selector) {
      functionName = 'transferFrom';
    } else if (functionSelector == _CDAI.transfer.selector) {
      functionName = 'transfer';
    } else {
      functionName = 'an unknown function';
    }
  }


/**
 * @notice Internal pure function to decode revert reasons. The revert reason
 * prefix is removed and the remaining string argument is decoded.
 * @param revertData bytes The raw data supplied alongside the revert.
 * @return The decoded revert reason string.
 */

function _decodeRevertReason(
    bytes memory revertData
  ) internal pure returns (string memory revertReason) {
    // Solidity prefixes revert reason with 0x08c379a0 -> Error(string) selector
    if (
      revertData.length > 68 && // prefix (4) + position (32) + length (32)
      revertData[0] == byte(0x08) &&
      revertData[1] == byte(0xc3) &&
      revertData[2] == byte(0x79) &&
      revertData[3] == byte(0xa0)
    ) {
      // Get the revert reason without the prefix from the revert data.
      bytes memory revertReasonBytes = new bytes(revertData.length - 4);
      for (uint256 i = 4; i < revertData.length; i++) {
        revertReasonBytes[i - 4] = revertData[i];
      }

      // Decode the resultant revert reason as a string.
      revertReason = abi.decode(revertReasonBytes, (string));
    } else {
      // Simply return the default, with no revert reason.
      revertReason = "(no revert reason)";
    }
  }


// ------------------------------------------------------------------------
// Token holders can stake their tokens using this function
// @param tokens number of tokens to stake
// ------------------------------------------------------------------------

function STAKE(uint256 tokens) external nonReentrant {
        require(IERC20(TRY).transferFrom(msg.sender, address(this), tokens), "Tokens cannot be transferred from user for locking");

            uint256 transferTxFee = (onePercent(tokens).mul(txFee)).div(10);
            uint256 tokensToStake = (tokens.sub(transferTxFee));

            // add pending rewards to remainder to be claimed by user later, if there is any existing stake
            uint256 owing = pendingReward(msg.sender);
            stakers[msg.sender].remainder += owing;

            stakers[msg.sender].stakedTokens = tokensToStake.add(stakers[msg.sender].stakedTokens);
            stakers[msg.sender].lastDividends = owing;
            stakers[msg.sender].fromTotalDividend= totalDividends;
            stakers[msg.sender].round =  round;

            totalStakes = totalStakes.add(tokensToStake);

            addStakeholder(msg.sender);

            emit STAKED(msg.sender, tokens);

    }


// ------------------------------------------------------------------------
// Private function to register payouts
// ------------------------------------------------------------------------

function _addPayout(uint256 tokens_) private{
        // divide the funds among the currently staked tokens
        // scale the deposit and add the previous remainder
        uint256 available = (tokens_.mul(scaling)).add(scaledRemainder);
        uint256 dividendPerToken = available.div(totalStakes);
        scaledRemainder = available.mod(totalStakes);

        totalDividends = totalDividends.add(dividendPerToken);
        payouts[round] = payouts[round - 1].add(dividendPerToken);

        emit PAYOUT(round, tokens_, msg.sender);
        round++;
    }


// ------------------------------------------------------------------------
// Stakers can claim their pending rewards using this function
// ------------------------------------------------------------------------

function CLAIMREWARD() public nonReentrant{

        if(totalDividends > stakers[msg.sender].fromTotalDividend){
            uint256 owing = pendingReward(msg.sender);

            owing = owing.add(stakers[msg.sender].remainder);
            stakers[msg.sender].remainder = 0;

            require(IERC20(fETH).transfer(msg.sender,owing), "ERROR: error in sending reward from contract");

            emit CLAIMEDREWARD(msg.sender, owing);

            stakers[msg.sender].lastDividends = owing; // unscaled
            stakers[msg.sender].round = round; // update the round
            stakers[msg.sender].fromTotalDividend = totalDividends; // scaled
        }
    }


// ------------------------------------------------------------------------
// Get the pending rewards of the staker
// @param _staker the address of the staker
// ------------------------------------------------------------------------

function pendingReward(address staker) private returns (uint256) {
        require(staker != address(0), "ERC20: sending to the zero address");

        uint stakersRound = stakers[staker].round;
        uint256 amount =  ((totalDividends.sub(payouts[stakersRound - 1])).mul(stakers[staker].stakedTokens)).div(scaling);
        stakers[staker].remainder += ((totalDividends.sub(payouts[stakersRound - 1])).mul(stakers[staker].stakedTokens)) % scaling ;
        return amount;
    }


// ------------------------------------------------------------------------
// Stakers can un stake the staked tokens using this function
// @param tokens the number of tokens to withdraw
// ------------------------------------------------------------------------

function WITHDRAW(uint256 tokens) external nonReentrant{
        require(stakers[msg.sender].stakedTokens >= tokens && tokens > 0, "Invalid token amount to withdraw");

        totalStakes = totalStakes.sub(tokens);

        // add pending rewards to remainder to be claimed by user later, if there is any existing stake
        uint256 owing = pendingReward(msg.sender);
        stakers[msg.sender].remainder += owing;

        stakers[msg.sender].stakedTokens = stakers[msg.sender].stakedTokens.sub(tokens);
        stakers[msg.sender].lastDividends = owing;
        stakers[msg.sender].fromTotalDividend= totalDividends;
        stakers[msg.sender].round =  round;

        require(IERC20(TRY).transfer(msg.sender, tokens), "Error in un-staking tokens");
        emit UNSTAKED(msg.sender, tokens);

        if(perform==true) {
        regreward(regrewardContract).distributeAll();
        }
    }


/// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
/// @param masterCopy Address of master copy.
/// @param data Payload for message call sent to new proxy contract.

function createProxy(address masterCopy, bytes memory data)
        public
        returns (Proxy proxy)
    {
        proxy = new Proxy(masterCopy);
        if (data.length > 0)
            // solium-disable-next-line security/no-inline-assembly
            assembly {
                if eq(call(gas, proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) }
            }
        emit ProxyCreation(proxy);
    }


/// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.
///      This method is only meant as an utility to be called from other methods
/// @param _mastercopy Address of master copy.
/// @param initializer Payload for message call sent to new proxy contract.
/// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.

function deployProxyWithNonce(address _mastercopy, bytes memory initializer, uint256 saltNonce)
        internal
        returns (Proxy proxy)
    {
        // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
        bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint256(_mastercopy));
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
	require(address(proxy) != address(0), "Create2 call failed");
    }


/// @notice Migrate tokens to the new token contract.
/// @dev Required state: Operational Migration
/// @param _value The amount of token to be migrated

function migrate(uint256 _value) external {
        // Abort if not in Operational Migration state.
        if (funding) throw;
        if (migrationAgent == 0) throw;

        // Validate input value.
        if (_value == 0) throw;
        if (_value > balances[msg.sender]) throw;

        balances[msg.sender] -= _value;
        totalTokens -= _value;
        totalMigrated += _value;
        MigrationAgent(migrationAgent).migrateFrom(msg.sender, _value);
        Migrate(msg.sender, migrationAgent, _value);
    }


/// @notice Finalize crowdfunding
/// @dev If cap was reached or crowdfunding has ended then:
/// create GNT for the Golem Factory and developer,
/// transfer ETH to the Golem Factory address.
/// @dev Required state: Funding Success
/// @dev State transition: -> Operational Normal

function finalize() external {
        // Abort if not in Funding Success state.
        if (!funding) throw;
        if ((block.number <= fundingEndBlock ||
             totalTokens < tokenCreationMin) &&
            totalTokens < tokenCreationCap) throw;

        // Switch to Operational state. This is the only place this can happen.
        funding = false;

        // Create additional GNT for the Golem Factory and developers as
        // the 18% of total number of tokens.
        // All additional tokens are transfered to the account controller by
        // GNTAllocation contract which will not allow using them for 6 months.
        uint256 percentOfTotal = 18;
        uint256 additionalTokens =
            totalTokens * percentOfTotal / (100 - percentOfTotal);
        totalTokens += additionalTokens;
        balances[lockedAllocation] += additionalTokens;
        Transfer(0, lockedAllocation, additionalTokens);

        // Transfer ETH to the Golem Factory address.
        if (!golemFactory.send(this.balance)) throw;
    }


/// @notice Get back the ether sent during the funding in case the funding
/// has not reached the minimum level.
/// @dev Required state: Funding Failure

function refund() external {
        // Abort if not in Funding Failure state.
        if (!funding) throw;
        if (block.number <= fundingEndBlock) throw;
        if (totalTokens >= tokenCreationMin) throw;

        var gntValue = balances[msg.sender];
        if (gntValue == 0) throw;
        balances[msg.sender] = 0;
        totalTokens -= gntValue;

        var ethValue = gntValue / tokenCreationRate;
        Refund(msg.sender, ethValue);
        if (!msg.sender.send(ethValue)) throw;
    }


/// @notice Allow developer to unlock allocated tokens by transferring them
/// from GNTAllocation to developer's address.

function unlock() external {
        if (now < unlockedAt) throw;

        // During first unlock attempt fetch total number of locked tokens.
        if (tokensCreated == 0)
            tokensCreated = gnt.balanceOf(this);

        var allocation = allocations[msg.sender];
        allocations[msg.sender] = 0;
        var toTransfer = tokensCreated * allocation / totalAllocations;

        // Will fail if allocation (and therefore toTransfer) is 0.
        if (!gnt.transfer(msg.sender, toTransfer)) throw;
    }


///@dev the main function to be executed
///@param _minReqBurnt  REQ token needed to be burned.
///@param _deadline  maximum timestamp to accept the trade from the router

function burn(uint _minReqBurnt, uint256 _deadline)
        external
        returns(uint)
    {
        IERC20 dai = IERC20(LOCKED_TOKEN_ADDRESS);
        IBurnableErc20 req = IBurnableErc20(BURNABLE_TOKEN_ADDRESS);
        uint daiToConvert = dai.balanceOf(address(this));

        if (_deadline == 0) {
            _deadline = block.timestamp + 1000;
        }

        // 1 step swapping path (only works if there is a sufficient liquidity behind the router)
        address[] memory path = new address[](2);
        path[0] = LOCKED_TOKEN_ADDRESS;
        path[1] = BURNABLE_TOKEN_ADDRESS;

        // Do the swap and get the amount of REQ purchased
        uint reqToBurn = swapRouter.swapExactTokensForTokens(
          daiToConvert,
          _minReqBurnt,
          path,
          address(this),
          _deadline
        )[1];

        // Burn all the purchased REQ and return this amount
        req.burn(reqToBurn);
        return reqToBurn;
    }


/**
 * @dev for mint function
 * @param account The address to get the reward.
 * @param amount The amount of the reward.
 */

function _mint(address account, uint256 amount) internal
    {
        require(account != address(0), "ERC20: mint to the zero address");
        require(_whitelist[account] > 0, "must in whitelist");

        _allGotBalances = _allGotBalances.add(amount);
        _gotBalances[account] = _gotBalances[account].add(amount);

        _dego.mint(account, amount);
        emit Mint(account, amount);
    }


/**
 * @dev  Calculate and reuturn Unclaimed rewards
 */

function earned(address account) public view returns (uint256)
   {
        require(_whitelist[account] > 0, "must in whitelist");

        uint256 reward = 0;
        uint256 accountTotal = _whitelist[account];
        uint256 rewardPerSecond = accountTotal.mul(_rewardRate2).div(_baseRate).div(_rewardDurationTime);
        uint256 lastRewardTime = _lastRewardTimes[account];

        // fist time get 20%
        if( lastRewardTime == 0 ){
            uint256 reward1 = accountTotal.mul(_rewardRate1).div(_baseRate);
            uint256 durationTime = block.timestamp.sub(_startTime);
            uint256 reward2 = durationTime.mul(rewardPerSecond);
            reward = reward1 + reward2;
            return reward;
        }

        uint256 durationTime = block.timestamp.sub(lastRewardTime);
        reward = durationTime.mul(rewardPerSecond);
        return reward;
   }


// (base^exponent) % FIELD_SIZE
// Cribbed from https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4

function bigModExp(uint256 base, uint256 exponent) internal view returns (uint256 exponentiation) {
        uint256 callResult;
        uint256[6] memory bigModExpContractInputs;
        bigModExpContractInputs[0] = WORD_LENGTH_BYTES; // Length of base
        bigModExpContractInputs[1] = WORD_LENGTH_BYTES; // Length of exponent
        bigModExpContractInputs[2] = WORD_LENGTH_BYTES; // Length of modulus
        bigModExpContractInputs[3] = base;
        bigModExpContractInputs[4] = exponent;
        bigModExpContractInputs[5] = FIELD_SIZE;
        uint256[1] memory output;
        assembly {
            // solhint-disable-line no-inline-assembly
            callResult := staticcall(
                not(0), // Gas cost: no limit
                0x05, // Bigmodexp contract address
                bigModExpContractInputs,
                0xc0, // Length of input segment: 6*0x20-bytes
                output,
                0x20 // Length of output segment
            )
        }
        if (callResult == 0) {
            revert("bigModExp failure!");
        }
        return output[0];
    }


// Hash x uniformly into {0, ..., FIELD_SIZE-1}.

function fieldHash(bytes memory b) internal pure returns (uint256 x_) {
        x_ = uint256(keccak256(b));
        // Rejecting if x >= FIELD_SIZE corresponds to step 2.1 in section 2.3.4 of
        // http://www.secg.org/sec1-v2.pdf , which is part of the definition of
        // string_to_point in the IETF draft
        while (x_ >= FIELD_SIZE) {
            x_ = uint256(keccak256(abi.encodePacked(x_)));
        }
    }


// Hash b to a random point which hopefully lies on secp256k1.

function newCandidateSecp256k1Point(bytes memory b) internal view returns (uint256[2] memory p) {
        p[0] = fieldHash(b);
        p[1] = squareRoot(ySquared(p[0]));
        if (p[1] % 2 == 1) {
            p[1] = FIELD_SIZE - p[1];
        }
    }


/** *********************************************************************
 * @notice Check that product==scalar*multiplicand
 *
 * @dev Based on Vitalik Buterin's idea in ethresear.ch post cited below.
 *
 * @param multiplicand: secp256k1 point
 * @param scalar: non-zero GF(GROUP_ORDER) scalar
 * @param product: secp256k1 expected to be multiplier * multiplicand
 * @return verifies true iff product==scalar*multiplicand, with cryptographically high probability
 */

function ecmulVerify(
        uint256[2] memory multiplicand,
        uint256 scalar,
        uint256[2] memory product
    ) internal pure returns (bool verifies) {
        require(scalar != 0, "scalar must not be 0"); // Rules out an ecrecover failure case
        uint256 x = multiplicand[0]; // x ordinate of multiplicand
        uint8 v = multiplicand[1] % 2 == 0 ? 27 : 28; // parity of y ordinate
        // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
        // Point corresponding to address ecrecover(0, v, x, s=scalar*x) is
        // (x⁻¹ mod GROUP_ORDER) * (scalar * x * multiplicand - 0 * g), i.e.
        // scalar*multiplicand. See https://crypto.stackexchange.com/a/18106
        bytes32 scalarTimesX = bytes32(mulmod(scalar, x, GROUP_ORDER));
        address actual = ecrecover(bytes32(0), v, bytes32(x), scalarTimesX);
        // Explicit conversion to address takes bottom 160 bits
        address expected = address(uint256(keccak256(abi.encodePacked(product))));
        return (actual == expected);
    }


// p1+p2, as affine points on secp256k1.
//
// invZ must be the inverse of the z returned by projectiveECAdd(p1, p2).
// It is computed off-chain to save gas.
//
// p1 and p2 must be distinct, because projectiveECAdd doesn't handle
// point doubling.

function affineECAdd(
        uint256[2] memory p1,
        uint256[2] memory p2,
        uint256 invZ
    ) internal pure returns (uint256[2] memory) {
        uint256 x;
        uint256 y;
        uint256 z;
        (x, y, z) = projectiveECAdd(p1[0], p1[1], p2[0], p2[1]);
        require(mulmod(z, invZ, FIELD_SIZE) == 1, "invZ must be inverse of z");
        // Clear the z ordinate of the projective representation by dividing through
        // by it, to obtain the affine representation
        return [mulmod(x, invZ, FIELD_SIZE), mulmod(y, invZ, FIELD_SIZE)];
    }


// True iff address(c*p+s*g) == lcWitness, where g is generator. (With
// cryptographically high probability.)

function verifyLinearCombinationWithGenerator(
        uint256 c,
        uint256[2] memory p,
        uint256 s,
        address lcWitness
    ) internal pure returns (bool) {
        // Rule out ecrecover failure modes which return address 0.
        require(lcWitness != address(0), "bad witness");
        uint8 v = (p[1] % 2 == 0) ? 27 : 28; // parity of y-ordinate of p
        bytes32 pseudoHash = bytes32(GROUP_ORDER - mulmod(p[0], s, GROUP_ORDER)); // -s*p[0]
        bytes32 pseudoSignature = bytes32(mulmod(c, p[0], GROUP_ORDER)); // c*p[0]
        // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/9
        // The point corresponding to the address returned by
        // ecrecover(-s*p[0],v,p[0],c*p[0]) is
        // (p[0]⁻¹ mod GROUP_ORDER)*(c*p[0]-(-s)*p[0]*g)=c*p+s*g.
        // See https://crypto.stackexchange.com/a/18106
        // https://bitcoin.stackexchange.com/questions/38351/ecdsa-v-r-s-what-is-v
        address computed = ecrecover(pseudoHash, v, bytes32(p[0]), pseudoSignature);
        return computed == lcWitness;
    }


// c*p1 + s*p2. Requires cp1Witness=c*p1 and sp2Witness=s*p2. Also
// requires cp1Witness != sp2Witness (which is fine for this application,
// since it is cryptographically impossible for them to be equal. In the
// (cryptographically impossible) case that a prover accidentally derives
// a proof with equal c*p1 and s*p2, they should retry with a different
// proof nonce.) Assumes that all points are on secp256k1
// (which is checked in verifyVORProof below.)

function linearCombination(
        uint256 c,
        uint256[2] memory p1,
        uint256[2] memory cp1Witness,
        uint256 s,
        uint256[2] memory p2,
        uint256[2] memory sp2Witness,
        uint256 zInv
    ) internal pure returns (uint256[2] memory) {
        require((cp1Witness[0] - sp2Witness[0]) % FIELD_SIZE != 0, "points in sum must be distinct");
        require(ecmulVerify(p1, c, cp1Witness), "First multiplication check failed");
        require(ecmulVerify(p2, s, sp2Witness), "Second multiplication check failed");
        return affineECAdd(cp1Witness, sp2Witness, zInv);
    }


// Pseudo-random number from inputs.
// TODO(alx): We could save a bit of gas by following the standard here and
// using the compressed representation of the points, if we collated the y
// parities into a single bytes32.
// https://www.pivotaltracker.com/story/show/171120588

function scalarFromCurvePoints(
        uint256[2] memory hash,
        uint256[2] memory pk,
        uint256[2] memory gamma,
        address uWitness,
        uint256[2] memory v
    ) internal pure returns (uint256 s) {
        return
            uint256(
                keccak256(
                    abi.encodePacked(
                        SCALAR_FROM_CURVE_POINTS_HASH_PREFIX,
                        hash,
                        pk,
                        gamma,
                        v,
                        uWitness
                    )
                )
            );
    }


// True if (gamma, c, s) is a correctly constructed randomness proof from pk
// and seed. zInv must be the inverse of the third ordinate from
// projectiveECAdd applied to cGammaWitness and sHashWitness. Corresponds to
// section 5.3 of the IETF draft.
//
// TODO(alx): Since I'm only using pk in the ecrecover call, I could only pass
// the x ordinate, and the parity of the y ordinate in the top bit of uWitness
// (which I could make a uint256 without using any extra space.) Would save
// about 2000 gas. https://www.pivotaltracker.com/story/show/170828567

function verifyVORProof(
        uint256[2] memory pk,
        uint256[2] memory gamma,
        uint256 c,
        uint256 s,
        uint256 seed,
        address uWitness,
        uint256[2] memory cGammaWitness,
        uint256[2] memory sHashWitness,
        uint256 zInv
    ) internal view {
        require(isOnCurve(pk), "public key is not on curve");
        require(isOnCurve(gamma), "gamma is not on curve");
        require(isOnCurve(cGammaWitness), "cGammaWitness is not on curve");
        require(isOnCurve(sHashWitness), "sHashWitness is not on curve");
        require(verifyLinearCombinationWithGenerator(c, pk, s, uWitness), "addr(c*pk+s*g)≠_uWitness");
        // Step 4. of IETF draft section 5.3 (pk corresponds to Y, seed to alpha_string)
        uint256[2] memory hash = hashToCurve(pk, seed);
        // Step 6. of IETF draft section 5.3, but see note for step 5 about +/- terms
        uint256[2] memory v =
            linearCombination(
                c,
                gamma,
                cGammaWitness,
                s,
                hash,
                sHashWitness,
                zInv
            );
        // Steps 7. and 8. of IETF draft section 5.3
        uint256 derivedC = scalarFromCurvePoints(hash, pk, gamma, uWitness, v);
        require(c == derivedC, "invalid proof");
    }


/* ***************************************************************************
 * @notice Returns proof's output, if proof is valid. Otherwise reverts

 * @param proof A binary-encoded proof
 *
 * Throws if proof is invalid, otherwise:
 * @return output i.e., the random output implied by the proof
 * ***************************************************************************
 * @dev See the calculation of PROOF_LENGTH for the binary layout of proof.
 */

function randomValueFromVORProof(bytes memory proof) internal view returns (uint256 output) {
        require(proof.length == PROOF_LENGTH, "wrong proof length");

        uint256[2] memory pk; // parse proof contents into these variables
        uint256[2] memory gamma;
        // c, s and seed combined (prevents "stack too deep" compilation error)
        uint256[3] memory cSSeed;
        address uWitness;
        uint256[2] memory cGammaWitness;
        uint256[2] memory sHashWitness;
        uint256 zInv;

        (pk, gamma, cSSeed, uWitness, cGammaWitness, sHashWitness, zInv) =
            abi.decode(proof,
                (
                    uint256[2],
                    uint256[2],
                    uint256[3],
                    address,
                    uint256[2],
                    uint256[2],
                    uint256
                )
            );

        verifyVORProof(
            pk,
            gamma,
            cSSeed[0], // c
            cSSeed[1], // s
            cSSeed[2], // seed
            uWitness,
            cGammaWitness,
            sHashWitness,
            zInv
        );

        output = uint256(keccak256(abi.encode(VOR_RANDOM_OUTPUT_HASH_PREFIX, gamma)));
    }


/**
 * @notice Commits calling address to serve randomness
 * @param _fee minimum xFUND payment required to serve randomness
 * @param _oracle the address of the node with the proving key
 * @param _publicProvingKey public key used to prove randomness
 */

function registerProvingKey(
        uint256 _fee,
        address payable _oracle,
        uint256[2] calldata _publicProvingKey
    ) external {
        bytes32 keyHash = hashOfKey(_publicProvingKey);
        address oldVOROracle = serviceAgreements[keyHash].vOROracle;
        require(oldVOROracle == address(0), "please register a new key");
        require(_oracle != address(0), "_oracle must not be 0x0");
        serviceAgreements[keyHash].vOROracle = _oracle;

        require(_fee > 0, "fee cannot be zero");
        require(_fee <= 1e9 ether, "fee too high");
        serviceAgreements[keyHash].fee = uint96(_fee);
        emit NewServiceAgreement(keyHash, _fee);
    }


/**
 * @notice Changes the provider's base fee
 * @param _publicProvingKey public key used to prove randomness
 * @param _fee minimum xFUND payment required to serve randomness
 */

function changeFee(uint256[2] calldata _publicProvingKey, uint256 _fee) external {
        bytes32 keyHash = hashOfKey(_publicProvingKey);
        require(serviceAgreements[keyHash].vOROracle == _msgSender(), "only oracle can change the fee");
        require(_fee > 0, "fee cannot be zero");
        require(_fee <= 1e9 ether, "fee too high");
        serviceAgreements[keyHash].fee = uint96(_fee);
        emit ChangeFee(keyHash, _fee);
    }


/**
 * @notice Changes the provider's fee for a consumer contract
 * @param _publicProvingKey public key used to prove randomness
 * @param _fee minimum xFUND payment required to serve randomness
 */

function changeGranularFee(uint256[2] calldata _publicProvingKey, uint256 _fee, address _consumer) external {
        bytes32 keyHash = hashOfKey(_publicProvingKey);
        require(serviceAgreements[keyHash].vOROracle == _msgSender(), "only oracle can change the fee");
        require(_fee > 0, "fee cannot be zero");
        require(_fee <= 1e9 ether, "fee too high");
        serviceAgreements[keyHash].granularFees[_consumer] = uint96(_fee);
        emit ChangeGranularFee(keyHash, _consumer, _fee);
    }


/**
 * @notice creates the request for randomness
 *
 * @param _keyHash ID of the VOR public key against which to generate output
 * @param _consumerSeed Input to the VOR, from which randomness is generated
 * @param _feePaid Amount of xFUND sent with request. Must exceed fee for key
 *
 * @dev _consumerSeed is mixed with key hash, sender address and nonce to
 * @dev obtain preSeed, which is passed to VOR oracle, which mixes it with the
 * @dev hash of the block containing this request, to compute the final seed.
 *
 * @dev The requestId used to store the request data is constructed from the
 * @dev preSeed and keyHash.
 */

function randomnessRequest(
        bytes32 _keyHash,
        uint256 _consumerSeed,
        uint256 _feePaid
    ) external sufficientXFUND(_feePaid, _keyHash) {
        require(address(_msgSender()).isContract(), "request can only be made by a contract");

        xFUND.transferFrom(_msgSender(), address(this), _feePaid);

        uint256 nonce = nonces[_keyHash][_msgSender()];
        uint256 preSeed = makeVORInputSeed(_keyHash, _consumerSeed, _msgSender(), nonce);
        bytes32 requestId = makeRequestId(_keyHash, preSeed);

        // Cryptographically guaranteed by preSeed including an increasing nonce
        assert(callbacks[requestId].callbackContract == address(0));
        callbacks[requestId].callbackContract = _msgSender();

        assert(_feePaid < 1e27); // Total xFUND fits in uint96
        callbacks[requestId].randomnessFee = uint96(_feePaid);

        callbacks[requestId].seedAndBlockNum = keccak256(abi.encodePacked(preSeed, block.number));
        emit RandomnessRequest(_keyHash, preSeed, _msgSender(), _feePaid, requestId);
        nonces[_keyHash][_msgSender()] = nonces[_keyHash][_msgSender()].add(1);
    }


/**
 * @notice Called by the node to fulfill requests
 *
 * @param _proof the proof of randomness. Actual random output built from this
 */

function fulfillRandomnessRequest(bytes memory _proof) public {
        (bytes32 currentKeyHash, Callback memory callback, bytes32 requestId, uint256 randomness) =
            getRandomnessFromProof(_proof);

        // Pay oracle
        address payable oracle = serviceAgreements[currentKeyHash].vOROracle;
        withdrawableTokens[oracle] = withdrawableTokens[oracle].add(callback.randomnessFee);

        // Forget request. Must precede callback (prevents reentrancy)
        delete callbacks[requestId];
        callBackWithRandomness(requestId, randomness, callback.callbackContract);

        emit RandomnessRequestFulfilled(requestId, randomness);
    }


/**
 * @dev Internal function to remove a token ID from the list of a given address
 * This function is internal due to language limitations, see the note in ERC721.sol.
 * It is not intended to be called by custom derived contracts: in particular, it emits no Transfer event,
 * and doesn't clear approvals.
 * @param from address representing the previous owner of the given token ID
 * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
 */

function _removeTokenFrom(address from, uint256 tokenId) internal {
    super._removeTokenFrom(from, tokenId);

    // To prevent a gap in the array, we store the last token in the index of the token to delete, and
    // then delete the last slot.
    uint256 tokenIndex = _ownedTokensIndex[tokenId];
    uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
    uint256 lastToken = _ownedTokens[from][lastTokenIndex];

    _ownedTokens[from][tokenIndex] = lastToken;
    // This also deletes the contents at the last position of the array
    _ownedTokens[from].length--;

    // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
    // be zero. Then we can make sure that we will remove tokenId from the ownedTokens list since we are first swapping
    // the lastToken to the first position, and then dropping the element placed in the last position of the list

    _ownedTokensIndex[tokenId] = 0;
    _ownedTokensIndex[lastToken] = tokenIndex;
  }


/**
 * @dev Add Token Category for the tokenUri.
 *
 * @param _tokenUri string token URI of the category
 * @param _categoryId uint categoryid of the category
 * @param _maxQnty uint maximum quantity of tokens allowed to be minted
 * @param _price uint price tokens of that category will be sold at
 * @return True if success.
 */

function addTokenCategory(string _tokenUri, CategoryId _categoryId, uint _maxQnty, uint _price)
    public onlyOwner returns (bool success) {

    // price should be more than MIN_PRICE
    require(_price >= MIN_PRICE);

    // can't override existing category
    require(tokenCategories[_tokenUri].price == 0);

    tokenCategories[_tokenUri] = TokenCategory(_categoryId,
					       0, // zero tokens minted initially
					       _maxQnty,
					       _price);

    emit LogAddTokenCategory(_tokenUri, _categoryId, _maxQnty, _price);
    return true;
  }


/**
 * @dev Checks that it's possible to buy gift and mint token with the tokenURI.
 *
 * @param _tokenUri string token URI of the category
 * @param _transitAddress address transit address assigned to gift
 * @param _value uint amount of ether, that is send in tx.
 * @return True if success.
 */

function canBuyGift(string _tokenUri, address _transitAddress, uint _value) public view whenNotPaused returns (bool) {
    // can not override existing gift
    require(gifts[_transitAddress].status == Statuses.Empty);

    // eth covers NFT price
    TokenCategory memory category = tokenCategories[_tokenUri];
    require(_value >= category.price);

    // tokens of that type not sold out yet
    require(category.minted < category.maxQnty);

    return true;
  }


/**
 * @dev Buy gift and mint token with _tokenUri, new minted token will be kept in escrow
 * until receiver claims it.
 *
 * Received ether, splitted in 3 parts:
 *   - 0.01 ETH goes to ephemeral account, so it can pay gas fee for claim transaction.
 *   - token price (minus ephemeral account fee) goes to the Giveth Campaign as a donation.
 *   - Eth above token price is kept in the escrow, waiting for receiver to claim.
 *
 * @param _tokenUri string token URI of the category
 * @param _transitAddress address transit address assigned to gift
 * @param _msgHash string IPFS hash, where gift message stored at
 * @return True if success.
 */

function buyGift(string _tokenUri, address _transitAddress, string _msgHash)
          payable public whenNotPaused returns (bool) {

    require(canBuyGift(_tokenUri, _transitAddress, msg.value));

    // get token price from the category for that token URI
    uint tokenPrice = tokenCategories[_tokenUri].price;

    // ether above token price is for receiver to claim
    uint claimEth = msg.value.sub(tokenPrice);

    // mint new token
    uint tokenId = tokensCounter.add(1);
    nft.mintWithTokenURI(tokenId, _tokenUri);

    // increment counters
    tokenCategories[_tokenUri].minted = tokenCategories[_tokenUri].minted.add(1);
    tokensCounter = tokensCounter.add(1);

    // saving gift details
    gifts[_transitAddress] = Gift(
				  msg.sender,
				  claimEth,
				  tokenId,
				  Statuses.Deposited,
				  _msgHash
				  );

    // transfer small fee to ephemeral account to fund claim txs
    _transitAddress.transfer(EPHEMERAL_ADDRESS_FEE);

    // send donation to Giveth campaign
    uint donation = tokenPrice.sub(EPHEMERAL_ADDRESS_FEE);
    if (donation > 0) {
      bool donationSuccess = _makeDonation(msg.sender, donation);

      // revert if there was problem with sending ether to GivethBridge
      require(donationSuccess == true);
    }

    // log buy event
    emit LogBuy(
		_transitAddress,
		msg.sender,
		_tokenUri,
		tokenId,
		claimEth,
		tokenPrice);
    return true;
  }


/**
 * @dev Send donation to Giveth campaign
 * by calling function 'donateAndCreateGiver' of GivethBridge contract.
 *
 * @param _giver address giver address
 * @param _value uint donation amount (in wei)
 * @return True if success.
 */

function _makeDonation(address _giver, uint _value) internal returns (bool success) {
    bytes memory _data = abi.encodePacked(0x1870c10f, // function signature
					   bytes32(_giver),
					   bytes32(givethReceiverId),
					   bytes32(0),
					   bytes32(0));
    // make donation tx
    success = givethBridge.call.value(_value)(_data);
    return success;
  }


/**
 * @dev Get Gift assigned to transit address.
 *
 * @param _transitAddress address transit address assigned to gift
 * @return Gift details
 */

function getGift(address _transitAddress) public view returns (
	     uint256 tokenId,
	     string tokenUri,
	     address sender,  // gift buyer
	     uint claimEth,   // eth for receiver
	     uint nftPrice,   // token price
	     Statuses status, // gift status (deposited, claimed, cancelled)
	     string msgHash   // IPFS hash, where gift message stored at
    ) {
    Gift memory gift = gifts[_transitAddress];
    tokenUri =  nft.tokenURI(gift.tokenId);
    TokenCategory memory category = tokenCategories[tokenUri];
    return (
	    gift.tokenId,
	    tokenUri,
	    gift.sender,
	    gift.claimEth,
	    category.price,
	    gift.status,
	    gift.msgHash
	    );
  }


/**
 * @dev Cancel gift and get sent ether back. Only gift buyer can
 * cancel.
 *
 * @param _transitAddress transit address assigned to gift
 * @return True if success.
 */

function cancelGift(address _transitAddress) public returns (bool success) {
    Gift storage gift = gifts[_transitAddress];

    // is deposited and wasn't claimed or cancelled before
    require(gift.status == Statuses.Deposited);

    // only sender can cancel transfer;
    require(msg.sender == gift.sender);

    // update status to cancelled
    gift.status = Statuses.Cancelled;

    // transfer optional ether to receiver's address
    if (gift.claimEth > 0) {
      gift.sender.transfer(gift.claimEth);
    }

    // send nft to buyer
    nft.transferFrom(address(this), msg.sender, gift.tokenId);

    // log cancel event
    emit LogCancel(_transitAddress, msg.sender, gift.tokenId);

    return true;
  }


/**
 * @dev Claim gift to receiver's address if it is correctly signed
 * with private key for verification public key assigned to gift.
 *
 * @param _receiver address Signed address.
 * @return True if success.
 */

function claimGift(address _receiver) public whenNotPaused returns (bool success) {
    // only holder of ephemeral private key can claim gift
    address _transitAddress = msg.sender;

    Gift storage gift = gifts[_transitAddress];

    // is deposited and wasn't claimed or cancelled before
    require(gift.status == Statuses.Deposited);

    // update gift status to claimed
    gift.status = Statuses.Claimed;

    // send nft to receiver
    nft.transferFrom(address(this), _receiver, gift.tokenId);

    // transfer ether to receiver's address
    if (gift.claimEth > 0) {
      _receiver.transfer(gift.claimEth);
    }

    // log claim event
    emit LogClaim(_transitAddress, gift.sender, gift.tokenId, _receiver, gift.claimEth);

    return true;
  }


/// @dev Contract constructor sets owners, required number of confirmations, and recovery mode trigger.
/// @param _owners List of owners.
/// @param _required Number of required confirmations.
/// @param _recoveryModeTriggerTime Time (in seconds) of inactivity before recovery mode is triggerable.

function MultiSigWallet(address[] _owners, uint _required, uint _recoveryModeTriggerTime)
        public
    {
        for (uint i=0; i<_owners.length; i++) {
            if (isOwner[_owners[i]] || _owners[i] == 0)
                revert();
            isOwner[_owners[i]] = true;
        }
        owners = _owners;
        required = _required;
        lastTransactionTime = block.timestamp;
        recoveryModeTriggerTime = _recoveryModeTriggerTime;
    }


/// @dev Allows an owner to execute a confirmed transaction.
/// @param transactionId Transaction ID.

function executeTransaction(uint transactionId)
        public
        ownerExists(msg.sender)
        notExecuted(transactionId)
    {
        if (isConfirmed(transactionId)) {
            Transaction storage txn = transactions[transactionId];
            txn.executed = true;
            lastTransactionTime = block.timestamp;
            if (txn.destination.call.value(txn.value)(txn.data))
                Execution(transactionId);
            else {
                ExecutionFailure(transactionId);
                txn.executed = false;
            }
        }
    }


/**
 * @dev Set the fees in the system. All the fees are in mpip, except fixedTransferFee that is in wei. Level is between 0 and 2
 */

function setFees(uint256 _dailyStorageFee, uint256 _fixedTransferFee, uint256 _dynamicTransferFee, uint256 _mintingFee, uint8 level) external onlyOwner {
        require(level<=2, "Level: Please use level 0 to 2.");
        dailyStorageFee[level] = _dailyStorageFee;
        fixedTransferFee[level] = _fixedTransferFee;
        dynamicTransferFee[level] = _dynamicTransferFee;
        mintingFee = _mintingFee;
    }


/*
 override the internal _transfer function so that we can
 take the fee, and conditionally do the swap + liquditiy
 */

function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        // is the token balance of this contract address over the min number of
        // tokens that we need to initiate a swap + liquidity lock?
        // also, don't get caught in a circular liquidity event.
        // also, don't swap & liquify if sender is uniswap pair.
        uint256 contractTokenBalance = balanceOf(address(this));
        bool overMinTokenBalance = contractTokenBalance >= minTokensBeforeSwap;
        if (
            overMinTokenBalance &&
            !inSwapAndLiquify &&
            msg.sender != uniswapV2Pair &&
            swapAndLiquifyEnabled
        ) {
            swapAndLiquify(contractTokenBalance);
        }

        // calculate the number of tokens to take as a fee
        uint256 tokensToLock = calculateTokenFee(
            amount,
            feeDecimals,
            feePercentage
        );

        // take the fee and send those tokens to this contract address
        // and then send the remainder of tokens to original recipient
        uint256 tokensToTransfer = amount.sub(tokensToLock);

        super._transfer(from, address(this), tokensToLock);
        super._transfer(from, to, tokensToTransfer);

    }


/**
 * Sets the ABI associated with an ENS node.
 * Nodes may have one ABI of each content type. To remove an ABI, set it to
 * the empty string.
 * @param node The node to update.
 * @param contentType The content type of the ABI
 * @param data The ABI data.
 */

function setABI(bytes32 node, uint256 contentType, bytes calldata data) external authorised(node) {
        // Content types must be powers of 2
        require(((contentType - 1) & contentType) == 0);

        abis[node][contentType] = data;
        emit ABIChanged(node, contentType);
    }


/**
 * Returns the ABI associated with an ENS node.
 * Defined in EIP205.
 * @param node The ENS node to query
 * @param contentTypes A bitwise OR of the ABI formats accepted by the caller.
 * @return contentType The content type of the return value
 * @return data The ABI data
 */

function ABI(bytes32 node, uint256 contentTypes) external view returns (uint256, bytes memory) {
        mapping(uint256=>bytes) storage abiset = abis[node];

        for (uint256 contentType = 1; contentType <= contentTypes; contentType <<= 1) {
            if ((contentType & contentTypes) != 0 && abiset[contentType].length > 0) {
                return (contentType, abiset[contentType]);
            }
        }

        return (0, bytes(""));
    }


/*
 * @dev Returns a positive number if `other` comes lexicographically after
 *      `self`, a negative number if it comes before, or zero if the
 *      contents of the two bytes are equal. Comparison is done per-rune,
 *      on unicode codepoints.
 * @param self The first bytes to compare.
 * @param offset The offset of self.
 * @param len    The length of self.
 * @param other The second bytes to compare.
 * @param otheroffset The offset of the other string.
 * @param otherlen    The length of the other string.
 * @return The result of the comparison.
 */

function compare(bytes memory self, uint offset, uint len, bytes memory other, uint otheroffset, uint otherlen) internal pure returns (int) {
        uint shortest = len;
        if (otherlen < len)
        shortest = otherlen;

        uint selfptr;
        uint otherptr;

        assembly {
            selfptr := add(self, add(offset, 32))
            otherptr := add(other, add(otheroffset, 32))
        }
        for (uint idx = 0; idx < shortest; idx += 32) {
            uint a;
            uint b;
            assembly {
                a := mload(selfptr)
                b := mload(otherptr)
            }
            if (a != b) {
                // Mask out irrelevant bytes and check again
                uint mask;
                if (shortest > 32) {
                    mask = uint256(- 1); // aka 0xffffff....
                } else {
                    mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
                }
                uint diff = (a & mask) - (b & mask);
                if (diff != 0)
                return int(diff);
            }
            selfptr += 32;
            otherptr += 32;
        }

        return int(len) - int(otherlen);
    }


/*
 * @dev Returns the n byte value at the specified index of self.
 * @param self The byte string.
 * @param idx The index into the bytes.
 * @param len The number of bytes.
 * @return The specified 32 bytes of the string.
 */

function readBytesN(bytes memory self, uint idx, uint len) internal pure returns (bytes32 ret) {
        require(len <= 32);
        require(idx + len <= self.length);
        assembly {
            let mask := not(sub(exp(256, sub(32, len)), 1))
            ret := and(mload(add(add(self, 32), idx)),  mask)
        }
    }


/*
 * @dev Copies a substring into a new byte string.
 * @param self The byte string to copy from.
 * @param offset The offset to start copying at.
 * @param len The number of bytes to copy.
 */

function substring(bytes memory self, uint offset, uint len) internal pure returns(bytes memory) {
        require(offset + len <= self.length);

        bytes memory ret = new bytes(len);
        uint dest;
        uint src;

        assembly {
            dest := add(ret, 32)
            src := add(add(self, 32), offset)
        }
        memcpy(dest, src, len);

        return ret;
    }


/**
 * @dev Returns the number of bytes in the DNS name at 'offset' in 'self'.
 * @param self The byte array to read a name from.
 * @param offset The offset to start reading at.
 * @return The length of the DNS name at 'offset', in bytes.
 */

function nameLength(bytes memory self, uint offset) internal pure returns(uint) {
        uint idx = offset;
        while (true) {
            assert(idx < self.length);
            uint labelLen = self.readUint8(idx);
            idx += labelLen + 1;
            if (labelLen == 0) {
                break;
            }
        }
        return idx - offset;
    }


/**
 * @dev Moves the iterator to the next resource record.
 * @param iter The iterator to advance.
 */

function next(RRIterator memory iter) internal pure {
        iter.offset = iter.nextOffset;
        if (iter.offset >= iter.data.length) {
            return;
        }

        // Skip the name
        uint off = iter.offset + nameLength(iter.data, iter.offset);

        // Read type, class, and ttl
        iter.dnstype = iter.data.readUint16(off);
        off += 2;
        iter.class = iter.data.readUint16(off);
        off += 2;
        iter.ttl = iter.data.readUint32(off);
        off += 4;

        // Read the rdata
        uint rdataLength = iter.data.readUint16(off);
        off += 2;
        iter.rdataOffset = off;
        iter.nextOffset = off + rdataLength;
    }


/**
 * @dev Checks if a given RR type exists in a type bitmap.
 * @param self The byte string to read the type bitmap from.
 * @param offset The offset to start reading at.
 * @param rrtype The RR type to check for.
 * @return True if the type is found in the bitmap, false otherwise.
 */

function checkTypeBitmap(bytes memory self, uint offset, uint16 rrtype) internal pure returns (bool) {
        uint8 typeWindow = uint8(rrtype >> 8);
        uint8 windowByte = uint8((rrtype & 0xff) / 8);
        uint8 windowBitmask = uint8(uint8(1) << (uint8(7) - uint8(rrtype & 0x7)));
        for (uint off = offset; off < self.length;) {
            uint8 window = self.readUint8(off);
            uint8 len = self.readUint8(off + 1);
            if (typeWindow < window) {
                // We've gone past our window; it's not here.
                return false;
            } else if (typeWindow == window) {
                // Check this type bitmap
                if (len * 8 <= windowByte) {
                    // Our type is past the end of the bitmap
                    return false;
                }
                return (self.readUint8(off + windowByte + 2) & windowBitmask) != 0;
            } else {
                // Skip this type bitmap
                off += len + 2;
            }
        }

        return false;
    }


/**
 * Set one or more DNS records.  Records are supplied in wire-format.
 * Records with the same node/name/resource must be supplied one after the
 * other to ensure the data is updated correctly. For example, if the data
 * was supplied:
 *     a.example.com IN A 1.2.3.4
 *     a.example.com IN A 5.6.7.8
 *     www.example.com IN CNAME a.example.com.
 * then this would store the two A records for a.example.com correctly as a
 * single RRSET, however if the data was supplied:
 *     a.example.com IN A 1.2.3.4
 *     www.example.com IN CNAME a.example.com.
 *     a.example.com IN A 5.6.7.8
 * then this would store the first A record, the CNAME, then the second A
 * record which would overwrite the first.
 *
 * @param node the namehash of the node for which to set the records
 * @param data the DNS wire format records to set
 */

function setDNSRecords(bytes32 node, bytes calldata data) external authorised(node) {
        uint16 resource = 0;
        uint256 offset = 0;
        bytes memory name;
        bytes memory value;
        bytes32 nameHash;
        // Iterate over the data to add the resource records
        for (RRUtils.RRIterator memory iter = data.iterateRRs(0); !iter.done(); iter.next()) {
            if (resource == 0) {
                resource = iter.dnstype;
                name = iter.name();
                nameHash = keccak256(abi.encodePacked(name));
                value = bytes(iter.rdata());
            } else {
                bytes memory newName = iter.name();
                if (resource != iter.dnstype || !name.equals(newName)) {
                    setDNSRRSet(node, name, resource, data, offset, iter.offset - offset, value.length == 0);
                    resource = iter.dnstype;
                    offset = iter.offset;
                    name = newName;
                    nameHash = keccak256(name);
                    value = bytes(iter.rdata());
                }
            }
        }
        if (name.length > 0) {
            setDNSRRSet(node, name, resource, data, offset, data.length - offset, value.length == 0);
        }
    }


/**
 * Returns the address of a contract that implements the specified interface for this name.
 * If an implementer has not been set for this interfaceID and name, the resolver will query
 * the contract at `addr()`. If `addr()` is set, a contract exists at that address, and that
 * contract implements EIP165 and returns `true` for the specified interfaceID, its address
 * will be returned.
 * @param node The ENS node to query.
 * @param interfaceID The EIP 165 interface ID to check for.
 * @return The address that implements this interface, or 0 if the interface is unsupported.
 */

function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view returns (address) {
        address implementer = interfaces[node][interfaceID];
        if(implementer != address(0)) {
            return implementer;
        }

        address a = addr(node);
        if(a == address(0)) {
            return address(0);
        }

        (bool success, bytes memory returnData) = a.staticcall(abi.encodeWithSignature("supportsInterface(bytes4)", INTERFACE_META_ID));
        if(!success || returnData.length < 32 || returnData[31] == 0) {
            // EIP 165 not supported by target
            return address(0);
        }

        (success, returnData) = a.staticcall(abi.encodeWithSignature("supportsInterface(bytes4)", interfaceID));
        if(!success || returnData.length < 32 || returnData[31] == 0) {
            // Specified interface not supported by target
            return address(0);
        }

        return a;
    }


/**
 	 * @dev Advocate of Setting's _associatedTAOId submits an address setting update after an update has been proposed
 	 * @param _settingId The ID of the setting to be updated
 	 * @param _newValue The new address value for this setting
 	 * @param _proposalTAOId The child of the associatedTAOId with the update Logos
 	 * @param _signatureV The V part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @param _signatureR The R part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @param _signatureS The S part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @param _extraData Catch-all string value to be stored if exist
 	 */

function updateAddressSetting(
		uint256 _settingId,
		address _newValue,
		address _proposalTAOId,
		uint8 _signatureV,
		bytes32 _signatureR,
		bytes32 _signatureS,
		string memory _extraData)
		public
		canUpdate(_proposalTAOId)
		isAddressSetting(_settingId) {

		// Verify and store update address signature
		require (_verifyAndStoreUpdateAddressSignature(_settingId, _newValue, _proposalTAOId, _signatureV, _signatureR, _signatureS));

		// Store the setting state data
		require (_aoSettingAttribute.update(_settingId, _nameFactory.ethAddressToNameId(msg.sender), _proposalTAOId, _extraData));

		// Store the value as pending value
		_aoSettingValue.setPendingValue(_settingId, _newValue, false, '', '', 0);

		// Store the update hash key lookup
		_storeUpdateAddressHashLookup(_settingId, _newValue, _proposalTAOId, _extraData);

		emit SettingUpdate(_settingId, _nameFactory.ethAddressToNameId(msg.sender), _proposalTAOId);
	}


/**
 	 * @dev Advocate of Setting's proposalTAOId approves the setting update
 	 * @param _settingId The ID of the setting to be approved
 	 * @param _approved Whether to approve or reject
 	 */

function approveSettingUpdate(uint256 _settingId, bool _approved) public senderIsName senderNameNotCompromised {
		// Make sure setting exist
		require (_aoSetting.settingTypeLookup(_settingId) > 0);

		address _proposalTAOAdvocate = _nameFactory.ethAddressToNameId(msg.sender);
		(,,, address _proposalTAOId,,) = _aoSettingAttribute.getSettingState(_settingId);

		require (_aoSettingAttribute.approveUpdate(_settingId, _proposalTAOAdvocate, _approved));

		emit ApproveSettingUpdate(_settingId, _proposalTAOId, _proposalTAOAdvocate, _approved);
	}


/**
 	 * @dev Advocate of Setting's _associatedTAOId finalizes the setting update once the setting is approved
 	 * @param _settingId The ID of the setting to be finalized
 	 */

function finalizeSettingUpdate(uint256 _settingId) public senderIsName senderNameNotCompromised {
		// Make sure setting exist
		require (_aoSetting.settingTypeLookup(_settingId) > 0);

		address _associatedTAOAdvocate = _nameFactory.ethAddressToNameId(msg.sender);
		require (_aoSettingAttribute.finalizeUpdate(_settingId, _associatedTAOAdvocate));

		(,,, address _associatedTAOId,,,,,) = _aoSettingAttribute.getSettingData(_settingId);

		require (_aoSettingValue.movePendingToSetting(_settingId));

		emit FinalizeSettingUpdate(_settingId, _associatedTAOId, _associatedTAOAdvocate);
	}


/**
 	 * @dev Verify the signature for the address update and store the signature info
 	 * @param _settingId The ID of the setting to be updated
 	 * @param _newValue The new address value for this setting
 	 * @param _proposalTAOId The child of the associatedTAOId with the update Logos
 	 * @param _v The V part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @param _r The R part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @param _s The S part of the signature of proposalTAOId, newValue and associatedTAOId's Advocate
 	 * @return true if valid, false otherwise
 	 */

function _verifyAndStoreUpdateAddressSignature(
		uint256 _settingId,
		address _newValue,
		address _proposalTAOId,
		uint8 _v,
		bytes32 _r,
		bytes32 _s
		) internal returns (bool) {
		bytes32 _hash = keccak256(abi.encodePacked(address(this), _settingId, _proposalTAOId, _newValue, _nameFactory.ethAddressToNameId(msg.sender)));
		if (ecrecover(_hash, _v, _r, _s) != msg.sender) {
			return false;
		}
		_storeUpdateSignature(_settingId, _v, _r, _s);
		return true;
	}


// This is an incredibly trustfull ENS deployment, only use for testing

function newENS(address _owner) public returns (ENS) {
        ENS ens = new ENS();

        // Setup .eth TLD
        ens.setSubnodeOwner(ENS_ROOT, ETH_TLD_LABEL, this);

        // Setup public resolver
        PublicResolver resolver = new PublicResolver(ens);
        ens.setSubnodeOwner(ETH_TLD_NODE, PUBLIC_RESOLVER_LABEL, this);
        ens.setResolver(PUBLIC_RESOLVER_NODE, resolver);
        resolver.setAddr(PUBLIC_RESOLVER_NODE, resolver);

        ens.setOwner(ETH_TLD_NODE, _owner);
        ens.setOwner(ENS_ROOT, _owner);

        emit DeployENS(ens);

        return ens;
    }


/**
 	 * @dev Store the update hash lookup for this address setting
 	 * @param _settingId The ID of the setting to be updated
 	 * @param _newValue The new address value for this setting
 	 * @param _proposalTAOId The child of the associatedTAOId with the update Logos
 	 * @param _extraData Catch-all string value to be stored if exist
 	 */

function _storeUpdateAddressHashLookup(
		uint256 _settingId,
		address _newValue,
		address _proposalTAOId,
		string memory _extraData)
		internal {
		// Store the update hash key lookup
		(address _addressValue,,,,) = _aoSettingValue.settingValue(_settingId);
		updateHashLookup[keccak256(abi.encodePacked(address(this), _proposalTAOId, _addressValue, _newValue, _extraData, _settingId))] = _settingId;
	}


/**
 * Hack to get things to work automatically on OpenSea.
 * Use isApprovedForAll so the frontend doesn't have to worry about different method names.
 */

function isApprovedForAll(address _owner, address _operator)
        public
        view
        returns (bool)
    {
        if (owner() == _owner && _owner == _operator) {
            return true;
        }

        ProxyRegistry proxyRegistry = ProxyRegistry(proxyRegistryAddress);
        if (
            owner() == _owner &&
            address(proxyRegistry.proxies(_owner)) == _operator
        ) {
            return true;
        }
        return false;
    }


/* ///////////////////////////////////////////////////////////////
 EIP - 2612 LOGIC
 //////////////////////////////////////////////////////////////*/

function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > deadline) revert SignatureExpired();

        // cannot realistically overflow on human timescales
        unchecked {
            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);

            if (recoveredAddress == address(0) || recoveredAddress != owner) revert InvalidSignature();

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }