/// @dev Moves `value` AnyswapV3ERC20 token from account (`from`) to account (`to`) using allowance mechanism.
/// `value` is then deducted from caller account's allowance, unless set to `type(uint256).max`.
/// A transfer to `address(0)` triggers an ETH withdraw matching the sent AnyswapV3ERC20 token in favor of caller.
/// Emits {Approval} event to reflect reduced allowance `value` for caller account to spend from account (`from`),
/// unless allowance is set to `type(uint256).max`
/// Emits {Transfer} event.
/// Returns boolean value indicating whether operation succeeded.
/// Requirements:
///   - `from` account must have at least `value` balance of AnyswapV3ERC20 token.
///   - `from` account must have approved caller to spend at least `value` of AnyswapV3ERC20 token, unless `from` and caller are the same account.

function transferFrom(address from, address to, uint256 value) external override returns (bool) {
        require(to != address(0) || to != address(this));
        if (from != msg.sender) {
            // _decreaseAllowance(from, msg.sender, value);
            uint256 allowed = allowance[from][msg.sender];
            if (allowed != type(uint256).max) {
                require(allowed >= value, "AnyswapV3ERC20: request exceeds allowance");
                uint256 reduced = allowed - value;
                allowance[from][msg.sender] = reduced;
                emit Approval(from, msg.sender, reduced);
            }
        }

        uint256 balance = balanceOf[from];
        require(balance >= value, "AnyswapV3ERC20: transfer amount exceeds balance");

        balanceOf[from] = balance - value;
        balanceOf[to] += value;
        emit Transfer(from, to, value);

        return true;
    }


/// @dev Moves `value` AnyswapV3ERC20 token from caller's account to account (`to`),
/// after which a call is executed to an ERC677-compliant contract with the `data` parameter.
/// A transfer to `address(0)` triggers an ETH withdraw matching the sent AnyswapV3ERC20 token in favor of caller.
/// Emits {Transfer} event.
/// Returns boolean value indicating whether operation succeeded.
/// Requirements:
///   - caller account must have at least `value` AnyswapV3ERC20 token.
/// For more information on transferAndCall format, see https://github.com/ethereum/EIPs/issues/677.

function transferAndCall(address to, uint value, bytes calldata data) external override returns (bool) {
        require(to != address(0) || to != address(this));

        uint256 balance = balanceOf[msg.sender];
        require(balance >= value, "AnyswapV3ERC20: transfer amount exceeds balance");

        balanceOf[msg.sender] = balance - value;
        balanceOf[to] += value;
        emit Transfer(msg.sender, to, value);

        return ITransferReceiver(to).onTokenTransfer(msg.sender, value, data);
    }


//human 0.1 standard. Just an arbitrary versioning scheme.
//
//
//function name must match the contract name above

function GhostGold(
        ) {
        balances[msg.sender] = 1000000;               // Give the creator all initial tokens
        totalSupply = 1000000;                        // Update total supply
        name = "GhostGold";                           // Set the name for display purposes
        decimals = 0;                                 // Amount of decimals for display purposes
        symbol = "GXG";                               // Set the symbol for display purposes
    }


/*
 * creates a montage of the given token ids
 */

function createMontage(
        address creator,
        string memory _name,
        string memory _description,
        bool _canBeUnpacked,
        uint256[] memory _tokenIds
    ) external onlyOwner returns (uint256) {
        // create the montage object
        MontageData memory _montage = MontageData(creator, _name, _description, _canBeUnpacked, _tokenIds, "", "");

        // push the montage to the array
        montageDatas.push(_montage);
        uint256 _id = montageDatas.length - 1;
        return _id;
    }


/**
 * @dev Calculates a EIP712 domain separator.
 * @param name The EIP712 domain name.
 * @param version The EIP712 domain version.
 * @param verifyingContract The EIP712 verifying contract.
 * @return result EIP712 domain separator.
 */

function hashDomainSeperator(
        string memory name,
        string memory version,
        address verifyingContract
    ) internal view returns (bytes32 result) {
        bytes32 typehash = EIP712DOMAIN_TYPEHASH;

        // solhint-disable-next-line no-inline-assembly
        assembly {
            let nameHash := keccak256(add(name, 32), mload(name))
            let versionHash := keccak256(add(version, 32), mload(version))
            let chainId := chainid()

            let memPtr := mload(64)

            mstore(memPtr, typehash)
            mstore(add(memPtr, 32), nameHash)
            mstore(add(memPtr, 64), versionHash)
            mstore(add(memPtr, 96), chainId)
            mstore(add(memPtr, 128), verifyingContract)

            result := keccak256(memPtr, 160)
        }
    }


/**
 * @dev Calculates EIP712 encoding for a hash struct with a given domain hash.
 * @param domainHash Hash of the domain domain separator data, computed with getDomainHash().
 * @param hashStruct The EIP712 hash struct.
 * @return result EIP712 hash applied to the given EIP712 Domain.
 */

function hashMessage(bytes32 domainHash, bytes32 hashStruct) internal pure returns (bytes32 result) {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            let memPtr := mload(64)

            mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header
            mstore(add(memPtr, 2), domainHash) // EIP712 domain hash
            mstore(add(memPtr, 34), hashStruct) // Hash of struct

            result := keccak256(memPtr, 66)
        }
    }


/**
 * @notice Get the full contents of the montage (i.e. name and tokeids) for a given token ID.
 * @param tokenId the id of a previously minted montage
 */

function getMontageInformation(uint256 tokenId)
        public
        view
        onlyExistingTokens(tokenId)
        returns (
            address creator,
            string memory name,
            string memory description,
            bool canBeUnpacked,
            uint256[] memory tokenIds
        )
    {
        MontageData memory _montage = montageDatas[tokenId];
        tokenIds = _montage.tokenIds;
        name = _montage.name;
        description = _montage.description;
        canBeUnpacked = _montage.canBeUnpacked;
        creator = _montage.creator;
    }


/**
 * @dev Withdraw User Balance to user account, only Governance call it
 */

function userWithdraw(address withdrawAddress, uint256 amount)
        public
        payable
        onlyGovernance
    {
        require(
            _totalAmount >= amount,
            "User Balance should be more than withdraw amount."
        );

        // Send ETH From Contract to User Address
        (bool sent, ) = withdrawAddress.call{value: amount}("");
        require(sent, "Failed to Withdraw User.");

        // Update Total Balance
        _totalAmount = _totalAmount.sub(amount);
    }


/**
 * @dev EmergencyWithdraw when need to update contract and then will restore it
 */

function emergencyWithdraw() public payable onlyGovernance {
        require(_totalAmount > 0, "Can't send over total ETH amount.");

        uint256 amount = _totalAmount;
        address governanceAddress = governance();

        // Send ETH From Contract to Governance Address
        (bool sent, ) = governanceAddress.call{value: amount}("");
        require(sent, "Failed to Withdraw Governance");

        // Update Total Balance
        _totalAmount = 0;
    }


/**
 * @notice Verify a signed approval permit and execute if valid
 * @param owner     Token owner's address (Authorizer)
 * @param spender   Spender's address
 * @param value     Amount of allowance
 * @param deadline  The time at which this expires (unix time)
 * @param v         v of the signature
 * @param r         r of the signature
 * @param s         s of the signature
 */

function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external virtual {
        require(owner != address(0), "ERC2612/Invalid-address-0");
        require(deadline >= block.timestamp, "ERC2612/Expired-time");

        unchecked {
            bytes32 digest = EIP712.hashMessage(
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            );

            address recovered = ecrecover(digest, v, r, s);
            require(recovered != address(0) && recovered == owner, "ERC2612/Invalid-Signature");
        }

        _approve(owner, spender, value);
    }


/// @dev Tranfer tokens from one address to other
/// @param _from source address
/// @param _to dest address
/// @param _value tokens amount
/// @return transfer result

function transferFrom(address _from, address _to, uint _value) onlyPayloadSize(2 * 32) canTransfer checkFrozenAmount(_from, _value) returns (bool success) {
    var _allowance = allowed[_from][msg.sender];

    balances[_to] = safeAdd(balances[_to], _value);
    balances[_from] = safeSub(balances[_from], _value);
    allowed[_from][msg.sender] = safeSub(_allowance, _value);
    Transfer(_from, _to, _value);
    return true;
 }


/// @dev Approve transfer
/// @param _spender holder address
/// @param _value tokens amount
/// @return result

function approve(address _spender, uint _value) returns (bool success) {
   // To change the approve amount you first have to reduce the addresses`
   //  allowance to zero by calling `approve(_spender, 0)` if it is not
   //  already 0 to mitigate the race condition described here:
   //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   require ((_value == 0) || (allowed[msg.sender][_spender] == 0));

   allowed[msg.sender][_spender] = _value;
   Approval(msg.sender, _spender, _value);
   return true;
 }


/// @dev Constructor
/// @param _token Pay Fair token address
/// @param _multisigWallet team wallet
/// @param _start token ICO start date

function PayFairTokenCrowdsale(address _token, address _multisigWallet, uint _start) {
    require(_multisigWallet != 0);
    require(_start != 0);

    token = PayFairToken(_token);

    multisigWallet = _multisigWallet;
    startsAt = _start;

    milestones.push(Milestone(startsAt, startsAt + 1 days, 20));
    milestones.push(Milestone(startsAt + 1 days, startsAt + 5 days, 15));
    milestones.push(Milestone(startsAt + 5 days, startsAt + 10 days, 10));
    milestones.push(Milestone(startsAt + 10 days, startsAt + 20 days, 5));
  }


/// @dev Get the current milestone or bail out if we are not in the milestone periods.
/// @return Milestone current bonus milestone

function getCurrentMilestone() private constant returns (Milestone) {
    for (uint i = 0; i < milestones.length; i++) {
      if (milestones[i].start <= now && milestones[i].end > now) {
        return milestones[i];
      }
   }
 }


/// @dev Make an investment. Crowdsale must be running for one to invest.
/// @param receiver The Ethereum address who receives the tokens

function investInternal(address receiver) stopInEmergency private {
    var state = getState();
    require(state == State.Funding);
    require(msg.value > 0);

    // Add investment record
    var weiAmount = msg.value;
    investments.push(Investment(receiver, weiAmount, getCurrentMilestone().bonus + 100));
    investedAmountOf[receiver] = safeAdd(investedAmountOf[receiver], weiAmount);

    // Update totals
    weiRaised = safeAdd(weiRaised, weiAmount);
    // Transfer funds to the team wallet
    multisigWallet.transfer(weiAmount);
    // Tell us invest was success
    Invested(receiver, weiAmount);
  }


/// @dev Finalize a succcesful crowdsale.

function finalizeCrowdsale() internal {
    // Calculate divisor of the total token count
    uint divisor;
    for (uint i = 0; i < investments.length; i++)
       divisor = safeAdd(divisor, safeMul(investments[i].weiValue, investments[i].weight));

    uint localMultiplier = 10 ** 12;
    // Get unit price
    uint unitPrice = safeDiv(safeMul(token.convertToDecimal(TOTAL_ICO_TOKENS), localMultiplier), divisor);

    // Distribute tokens among investors
    for (i = 0; i < investments.length; i++) {
        var tokenAmount = safeDiv(safeMul(unitPrice, safeMul(investments[i].weiValue, investments[i].weight)), localMultiplier);
        tokenAmountOf[investments[i].source] += tokenAmount;
        assignTokens(investments[i].source, tokenAmount);
    }

    token.releaseTokenTransfer();
  }


/// @dev Crowdfund state machine management.
/// @return State current state

function getState() public constant returns (State) {
    if (finalized)
      return State.Finalized;
    if (address(token) == 0 || address(multisigWallet) == 0)
      return State.Preparing;
    if (preIcoTokensDistributed < token.convertToDecimal(PRE_ICO_MAX_TOKENS))
        return State.PreFunding;
    if (now >= startsAt && now < startsAt + ICO_DURATION && !isCrowdsaleFull())
      return State.Funding;
    if (isMinimumGoalReached())
      return State.Success;
    if (!isMinimumGoalReached() && weiRaised > 0 && loadedRefund >= weiRaised)
      return State.Refunding;
    return State.Failure;
  }


/**
 * @dev Will deploy a balancer LBP. Can only be called by DAO.agent address
 * @notice This function requires that the DAO.agent has approved this address for spending pool tokens (e.g. POP & USDC). It will revert if the DAO.agent has not approved this contract for spending those tokens. The PoolConfiguration.tokenAmounts for the pool tokens will be transferred from the DAO.agent to this contract and forwarded to the LBP
 */

function deployLBP() external {
    require(msg.sender == dao.agent, "Only DAO can call this");
    require(poolConfig.deployed != true, "The pool has already been deployed");
    require(_hasTokensForPool(), "Manager does not have enough pool tokens");

    poolConfig.deployed = true;

    lbp = ILBP(
      balancer.lbpFactory.create(
        poolConfig.name,
        poolConfig.symbol,
        poolConfig.tokens,
        poolConfig.startWeights,
        poolConfig.swapFee,
        poolConfig.owner,
        poolConfig.swapEnabledOnStart
      )
    );

    emit CreatedPool(address(lbp));

    uint256 endtime = poolConfig.startTime + poolConfig.durationInSeconds;

    lbp.updateWeightsGradually(poolConfig.startTime, endtime, poolConfig.endWeights);

    _joinPool();
  }


/**
 * @notice The DAO.agent can call this function to shutdown and unwind the pool. The proceeds will be forwarded to the DAO.treasury
 */

function withdrawFromPool() external {
    require(poolConfig.deployed, "Pool has not been deployed yet");
    require(msg.sender == dao.agent, "not today, buddy");

    bytes32 poolId = lbp.getPoolId();

    uint256[] memory minAmountsOut = new uint256[](poolConfig.tokens.length);
    for (uint256 i; i < poolConfig.tokens.length; i++) {
      minAmountsOut[i] = uint256(0);
    }

    lbp.setSwapEnabled(false);

    ExitPoolRequest memory request = ExitPoolRequest({
      assets: _convertERC20sToAssets(poolConfig.tokens),
      minAmountsOut: minAmountsOut,
      userData: abi.encode(uint256(WeightedPoolExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT), _getLPTokenBalance(poolId)),
      toInternalBalance: false
    });

    balancer.vault.exitPool(poolId, address(this), dao.treasury, request);
    emit ExitedPool(poolId);
  }


/**
 * @dev check contribution amount and time
 */

function isValidContribution() internal view returns(bool) {
        uint256 currentUserContribution = safeAdd(msg.value, userTotalContributed[msg.sender]);
        if(whiteList[msg.sender] && msg.value >= ETHER_MIN_CONTRIB) {
            if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB ) {
                    return false;
            }
            return true;

        }
        if(privilegedList[msg.sender] && msg.value >= ETHER_MIN_CONTRIB_PRIVATE) {
            if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB_PRIVATE ) {
                    return false;
            }
            return true;
        }

        if(token.limitedWallets(msg.sender) && msg.value >= ETHER_MIN_CONTRIB_USA) {
            if(now <= MAX_CONTRIB_CHECK_END_TIME && currentUserContribution > ETHER_MAX_CONTRIB_USA) {
                    return false;
            }
            return true;
        }

        return false;
    }


/**
 * @dev Check bnb contribution time, amount and hard cap overflow
 */

function isValidBNBContribution() internal view returns(bool) {
        if(token.limitedWallets(msg.sender)) {
            return false;
        }
        if(!whiteList[msg.sender] && !privilegedList[msg.sender]) {
            return false;
        }
        uint256 amount = bnbToken.allowance(msg.sender, address(this));
        if(amount < BNB_MIN_CONTRIB || safeAdd(totalBNBContributed, amount) > BNB_HARD_CAP) {
            return false;
        }
        return true;

    }


/**
 * @dev Calc bonus amount by contribution time
 */

function getBonus() internal constant returns (uint256, uint256) {
        uint256 numerator = 0;
        uint256 denominator = 100;

        if(now < BONUS_WINDOW_1_END_TIME) {
            numerator = 25;
        } else if(now < BONUS_WINDOW_2_END_TIME) {
            numerator = 15;
        } else if(now < BONUS_WINDOW_3_END_TIME) {
            numerator = 10;
        } else if(now < BONUS_WINDOW_4_END_TIME) {
            numerator = 5;
        } else {
            numerator = 0;
        }

        return (numerator, denominator);
    }


/**
 * @dev Process BNB token contribution
 * Transfer all amount of tokens approved by sender. Calc bonuses and issue tokens to contributor.
 */

function processBNBContribution() public whenNotPaused checkTime checkBNBContribution {
        bool additionalBonusApplied = false;
        uint256 bonusNum = 0;
        uint256 bonusDenom = 100;
        (bonusNum, bonusDenom) = getBonus();
        uint256 amountBNB = bnbToken.allowance(msg.sender, address(this));
        bnbToken.transferFrom(msg.sender, address(this), amountBNB);
        bnbContributions[msg.sender] = safeAdd(bnbContributions[msg.sender], amountBNB);

        uint256 tokenBonusAmount = 0;
        uint256 tokenAmount = safeDiv(safeMul(amountBNB, BNB_TOKEN_PRICE_NUM), BNB_TOKEN_PRICE_DENOM);
        rawTokenSupply = safeAdd(rawTokenSupply, tokenAmount);
        if(bonusNum > 0) {
            tokenBonusAmount = safeDiv(safeMul(tokenAmount, bonusNum), bonusDenom);
        }

        if(additionalBonusOwnerState[msg.sender] ==  AdditionalBonusState.Active) {
            additionalBonusOwnerState[msg.sender] = AdditionalBonusState.Applied;
            uint256 additionalBonus = safeDiv(safeMul(tokenAmount, ADDITIONAL_BONUS_NUM), ADDITIONAL_BONUS_DENOM);
            tokenBonusAmount = safeAdd(tokenBonusAmount, additionalBonus);
            additionalBonusApplied = true;
        }

        uint256 tokenTotalAmount = safeAdd(tokenAmount, tokenBonusAmount);
        token.issue(msg.sender, tokenTotalAmount);
        totalBNBContributed = safeAdd(totalBNBContributed, amountBNB);

        LogBNBContribution(msg.sender, amountBNB, tokenAmount, tokenBonusAmount, additionalBonusApplied, now);
    }


/**
 * @dev Process ether contribution. Calc bonuses and issue tokens to contributor.
 */

function processContribution(address contributor, uint256 amount) private checkTime checkContribution checkCap {
        bool additionalBonusApplied = false;
        uint256 bonusNum = 0;
        uint256 bonusDenom = 100;
        (bonusNum, bonusDenom) = getBonus();
        uint256 tokenBonusAmount = 0;

        uint256 tokenAmount = safeDiv(safeMul(amount, tokenPriceNum), tokenPriceDenom);
        rawTokenSupply = safeAdd(rawTokenSupply, tokenAmount);

        if(bonusNum > 0) {
            tokenBonusAmount = safeDiv(safeMul(tokenAmount, bonusNum), bonusDenom);
        }

        if(additionalBonusOwnerState[contributor] ==  AdditionalBonusState.Active) {
            additionalBonusOwnerState[contributor] = AdditionalBonusState.Applied;
            uint256 additionalBonus = safeDiv(safeMul(tokenAmount, ADDITIONAL_BONUS_NUM), ADDITIONAL_BONUS_DENOM);
            tokenBonusAmount = safeAdd(tokenBonusAmount, additionalBonus);
            additionalBonusApplied = true;
        }

        processPayment(contributor, amount, tokenAmount, tokenBonusAmount, additionalBonusApplied);
    }


/**
 * @dev Finalize crowdsale if we reached hard cap or current time > SALE_END_TIME
 */

function finalizeCrowdsale() public onlyOwner {
        if(
            (totalEtherContributed >= safeSub(hardCap, 20 ether) && totalBNBContributed >= safeSub(BNB_HARD_CAP, 10000 ether)) ||
            (now >= SALE_END_TIME && totalEtherContributed >= softCap)
        ) {
            fund.onCrowdsaleEnd();
            reservationFund.onCrowdsaleEnd();
            // BNB transfer
            bnbToken.transfer(bnbTokenWallet, bnbToken.balanceOf(address(this)));

            // Referral
            uint256 referralTokenAmount = safeDiv(rawTokenSupply, 10);
            token.issue(referralTokenWallet, referralTokenAmount);

            // Foundation
            uint256 foundationTokenAmount = safeDiv(token.totalSupply(), 2); // 20%
            lockedTokens.addTokens(foundationTokenWallet, foundationTokenAmount, now + 365 days);

            uint256 suppliedTokenAmount = token.totalSupply();

            // Reserve
            uint256 reservedTokenAmount = safeDiv(safeMul(suppliedTokenAmount, 3), 10); // 18%
            token.issue(address(lockedTokens), reservedTokenAmount);
            lockedTokens.addTokens(reserveTokenWallet, reservedTokenAmount, now + 183 days);

            // Advisors
            uint256 advisorsTokenAmount = safeDiv(suppliedTokenAmount, 10); // 6%
            token.issue(advisorsTokenWallet, advisorsTokenAmount);

            // Company
            uint256 companyTokenAmount = safeDiv(suppliedTokenAmount, 4); // 15%
            token.issue(address(lockedTokens), companyTokenAmount);
            lockedTokens.addTokens(companyTokenWallet, companyTokenAmount, now + 730 days);

            // Bounty
            uint256 bountyTokenAmount = safeDiv(suppliedTokenAmount, 60); // 1%
            token.issue(bountyTokenWallet, bountyTokenAmount);

            token.setAllowTransfers(true);

        } else if(now >= SALE_END_TIME) {
            // Enable fund`s crowdsale refund if soft cap is not reached
            fund.enableCrowdsaleRefund();
            reservationFund.onCrowdsaleEnd();
            bnbRefundEnabled = true;
        }
        token.finishIssuance();
    }


/** @notice Deposit ERC20 tokens into the smart contract (remember to set allowance in the token contract first)
 * @param  token   The address of the token to deposit
 * @param  amount  The amount of tokens to deposit
 */

function depositToken(address token, uint256 amount) public {
        Token(token).transferFrom(msg.sender, address(this), amount);    // Deposit ERC20
        require(
            checkERC20TransferSuccess(),                                 // Check whether the ERC20 token transfer was successful
            "ERC20 token transfer failed."
        );
        balances[token][msg.sender] += amount;                           // Credit the deposited token to users balance
        emit Deposit(msg.sender, token, amount);                         // Emit a deposit event
    }


/** @notice User-submitted transfer with arbiters signature, which sends tokens to another addresses account in the smart contract
 * @param  token              The token to transfer (ETH is address(address(0)))
 * @param  amount             The amount of tokens to transfer
 * @param  nonce              The nonce (to salt the hash)
 * @param  v                  Multi-signature v
 * @param  r                  Multi-signature r
 * @param  s                  Multi-signature s
 * @param  receiving_address  The address to transfer the token/ETH to
 */

function multiSigTransfer(address token, uint256 amount, uint64 nonce, uint8 v, bytes32 r, bytes32 s, address receiving_address) public {
        bytes32 hash = keccak256(abi.encodePacked(                      // Calculate the withdrawal/transfer hash from the parameters
            "\x19Ethereum Signed Message:\n32",
            keccak256(abi.encodePacked(
                msg.sender,
                token,
                amount,
                nonce,
                address(this)
            ))
        ));
        if(
            !processed_withdrawals[hash]                                // Check if the withdrawal was initiated before
            && arbiters[ecrecover(hash, v,r,s)]                         // Check if the multi-sig is valid
            && balances[token][msg.sender] >= amount                    // Check if the user holds the required balance
        ){
            processed_withdrawals[hash]         = true;                 // Mark this withdrawal as processed
            balances[token][msg.sender]        -= amount;               // Substract the balance from the withdrawing account
            balances[token][receiving_address] += amount;               // Add the balance to the receiving account

            blocked_for_single_sig_withdrawal[token][msg.sender] = 0;   // Set possible previous manual blocking of these funds to 0

            emit Withdrawal(msg.sender,token,amount);                   // Emit a Withdrawal event
            emit Deposit(receiving_address,token,amount);               // Emit a Deposit event
        }else{
            revert();                                                   // Revert the transaction if checks fail
        }
    }


/** @notice Allows user to block funds for single-sig withdrawal after 24h waiting period
 *         (This period is necessary to ensure all trades backed by these funds will be settled.)
 * @param  token   The address of the token to block (ETH is address(address(0)))
 * @param  amount  The amount of the token to block
 */

function blockFundsForSingleSigWithdrawal(address token, uint256 amount) public {
        if (balances[token][msg.sender] - blocked_for_single_sig_withdrawal[token][msg.sender] >= amount){  // Check if the user holds the required funds
            blocked_for_single_sig_withdrawal[token][msg.sender] += amount;         // Block funds for manual withdrawal
            last_blocked_timestamp[msg.sender] = block.timestamp;                   // Start 24h waiting period
            emit BlockedForSingleSigWithdrawal(msg.sender,token,amount);            // Emit BlockedForSingleSigWithdrawal event
        }else{
            revert();                                                               // Revert the transaction if the user does not hold the required balance
        }
    }


/** @notice Allows user to withdraw funds previously blocked after 24h
 */

function initiateSingleSigWithdrawal(address token, uint256 amount) public {
        if (
            balances[token][msg.sender] >= amount                                   // Check if the user holds the funds
            && blocked_for_single_sig_withdrawal[token][msg.sender] >= amount       // Check if these funds are blocked
            && last_blocked_timestamp[msg.sender] + 86400 <= block.timestamp        // Check if the one day waiting period has passed
        ){
            balances[token][msg.sender] -= amount;                                  // Substract the tokens from users balance
            blocked_for_single_sig_withdrawal[token][msg.sender] -= amount;         // Substract the tokens from users blocked balance
            if(token == address(0)){                                                // Withdraw ETH
                require(
                    msg.sender.send(amount),
                    "Sending of ETH failed."
                );
            }else{                                                                  // Withdraw ERC20 tokens
                Token(token).transfer(msg.sender, amount);                          // Transfer the ERC20 tokens
                require(
                    checkERC20TransferSuccess(),                                    // Check if the transfer was successful
                    "ERC20 token transfer failed."
                );
            }
            emit SingleSigWithdrawal(msg.sender,token,amount);                      // Emit a SingleSigWithdrawal event
        }else{
            revert();                                                               // Revert the transaction if the required checks fail
        }
    }


/*
 * @notice Creates proposal for voting.
 * @param _proposalText Text of the proposal.
 * @param _resultsInBlock Number of block on which results will be counted.
 */

function createVoting(
        string calldata _proposalText,
        uint _resultsInBlock
    ) onlyShareholder external returns (bool success){

        require(
            _resultsInBlock > block.number,
            "Block for results should be later than current block"
        );

        votingCounterForContract++;

        proposalText[votingCounterForContract] = _proposalText;
        resultsInBlock[votingCounterForContract] = _resultsInBlock;

        emit Proposal(votingCounterForContract, msg.sender, proposalText[votingCounterForContract], resultsInBlock[votingCounterForContract]);

        return true;
    }


/**
 * @dev Performs a low level call, to the contract holding all the logic, changing state on this contract at the same time
 */

function proxyCall() internal {
        address impl = implementation();

        assembly {
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
            returndatacopy(ptr, 0, returndatasize())

            switch result
                case 0 {
                    revert(ptr, returndatasize())
                }
                default {
                    return(ptr, returndatasize())
                }
        }
    }


/** @notice Give the user the option to perform multiple on-chain cancellations of orders at once with arbiters multi-sig
 * @param  orderHashes Array of orderHashes of the orders to be canceled
 * @param  v           Multi-sig v
 * @param  r           Multi-sig r
 * @param  s           Multi-sig s
 */

function multiSigOrderBatchCancel(bytes32[] orderHashes, uint8 v, bytes32 r, bytes32 s) public {
        if(
            arbiters[                                               // Check if the signee is an arbiter
                ecrecover(                                          // Restore the signing address
                    keccak256(abi.encodePacked(                     // Restore the signed hash (hash of all orderHashes)
                        "\x19Ethereum Signed Message:\n32",
                        keccak256(abi.encodePacked(orderHashes))
                    )),
                    v, r, s
                )
            ]
        ){
            uint len = orderHashes.length;
            for(uint8 i = 0; i < len; i++){
                matched[orderHashes[i]] = 2**256 - 1;               // Set the matched amount of all orders to the maximum
                emit OrderCanceled(orderHashes[i]);                 // emit OrderCanceled event
            }
        }else{
            revert();
        }
    }


/** @notice revoke the delegation of an address
 * @param  delegate  The delegated address
 * @param  v         Multi-sig v
 * @param  r         Multi-sig r
 * @param  s         Multi-sig s
 */

function revokeDelegation(address delegate, uint8 v, bytes32 r, bytes32 s) public {
        bytes32 hash = keccak256(abi.encodePacked(              // Restore the signed hash
            "\x19Ethereum Signed Message:\n32",
            keccak256(abi.encodePacked(
                delegate,
                msg.sender,
                address(this)
            ))
        ));

        require(arbiters[ecrecover(hash, v, r, s)], "MultiSig is not from known arbiter");  // Check if signee is an arbiter

        delegates[delegate] = address(1);       // set to 1 not 0 to prevent double delegation, which would make old signed order valid for the new delegator

        emit DelegateStatus(msg.sender, delegate, false);
    }


/** @notice Allows the feeCollector to directly withdraw his funds (would allow a fee distribution contract to withdraw collected fees)
 * @param  token   The token to withdraw
 * @param  amount  The amount of tokens to withdraw
 */

function feeWithdrawal(address token, uint256 amount) public {
        if (
            msg.sender == feeCollector                              // Check if the sender is the feeCollector
            && balances[token][feeCollector] >= amount              // Check if feeCollector has the sufficient balance
        ){
            balances[token][feeCollector] -= amount;                // Substract the feeCollectors balance
            if(token == address(0)){                                // Is the withdrawal token ETH
                require(
                    feeCollector.send(amount),                      // Withdraw ETH
                    "Sending of ETH failed."
                );
            }else{
                Token(token).transfer(feeCollector, amount);        // Withdraw ERC20
                require(                                            // Revert if the withdrawal failed
                    checkERC20TransferSuccess(),
                    "ERC20 token transfer failed."
                );
            }
            emit FeeWithdrawal(token,amount);                       // Emit FeeWithdrawal event
        }else{
            revert();                                               // Revert the transaction if the checks fail
        }
    }


/*
 * @notice Vote for the proposal
 * @param _proposalId Id (number) of the proposal
 */

function voteFor(uint256 _proposalId) onlyShareholder external returns (bool success){

        require(
            resultsInBlock[_proposalId] > block.number,
            "Voting already finished"
        );

        require(
            !boolVotedFor[_proposalId][msg.sender] && !boolVotedAgainst[_proposalId][msg.sender],
            "Already voted"
        );

        numberOfVotersFor[_proposalId] = numberOfVotersFor[_proposalId] + 1;

        uint voterId = numberOfVotersFor[_proposalId];

        votedFor[_proposalId][voterId] = msg.sender;

        boolVotedFor[_proposalId][msg.sender] = true;

        emit VoteFor(_proposalId, msg.sender);

        return true;
    }


// We have to check returndatasize after ERC20 tokens transfers, as some tokens are implemented badly (dont return a boolean)

function checkERC20TransferSuccess() pure private returns(bool){
        uint256 success = 0;

        assembly {
            switch returndatasize               // Check the number of bytes the token contract returned
                case 0 {                        // Nothing returned, but contract did not throw > assume our transfer succeeded
                    success := 1
                }
                case 32 {                       // 32 bytes returned, result is the returned bool
                    returndatacopy(0, 0, 32)
                    success := mload(0)
                }
        }

        return success != 0;
    }


/**
 @notice This function is used to add single-sided protected liquidity to BancorV2 Pool
 @dev This function stakes LPT Received so the
 @param _fromTokenAddress The token used for investment (address(0x00) if ether)
 @param _toBanConverterAddress The address of pool to add liquidity to
 @param _toReserveTokenAddress The Reserve to add liquidity to
 @param _amount The amount of ERC to invest
 @param _minPoolTokens received (minimum: 1)
 @param _allowanceTarget Spender for the swap
 @param _swapTarget Excecution target for the swap
 @param swapData DEX quote data
 @return LPT Received
 */

function ZapInSingleSided(
        address _fromTokenAddress,
        address _toBanConverterAddress,
        address _toReserveTokenAddress,
        uint256 _amount,
        uint256 _minPoolTokens,
        address _allowanceTarget,
        address _swapTarget,
        bytes calldata swapData
    )
        external
        payable
        nonReentrant
        stopInEmergency
        returns (uint256 lptReceived, uint256 id)
    {
        uint256 valueToSend;
        address tokenToSend;
        address poolAnchor = IBancorV2Converter(_toBanConverterAddress)
            .anchor();

        if (_fromTokenAddress == address(0)) {
            require(msg.value > 0, "ERR: No ETH sent");
            valueToSend = _transferGoodwill(_fromTokenAddress, msg.value);
        } else {
            require(_amount > 0, "Err: No Tokens Sent");
            require(msg.value == 0, "ERR: ETH sent with Token");
            tokenToSend = _fromTokenAddress;
            IERC20(tokenToSend).safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            valueToSend = _transferGoodwill(_fromTokenAddress, _amount);
        }

        if (_fromTokenAddress == _toReserveTokenAddress) {
            (lptReceived, id) = _enterBancor(
                poolAnchor,
                tokenToSend,
                valueToSend
            );
        } else {
            uint256 reserveTokensBought = _fillQuote(
                tokenToSend,
                _toReserveTokenAddress,
                _amount,
                _allowanceTarget,
                _swapTarget,
                swapData
            );
            (lptReceived, id) = _enterBancor(
                poolAnchor,
                _toReserveTokenAddress,
                reserveTokensBought
            );
        }

        require(lptReceived >= _minPoolTokens, "ERR: High Slippage");

        emit Zapin(msg.sender, poolAnchor, lptReceived, id);
    }


/**
 @dev This function is used to calculate and transfer goodwill
 @param _tokenContractAddress Token from which goodwill is deducted
 @param valueToSend The total value being zapped in
 @return The quantity of remaining tokens
 */

function _transferGoodwill(
        address _tokenContractAddress,
        uint256 valueToSend
    ) internal returns (uint256) {
        if (goodwill == 0) return valueToSend;

        uint256 goodwillPortion = SafeMath.div(
            SafeMath.mul(valueToSend, goodwill),
            10000
        );
        if (_tokenContractAddress == address(0)) {
            zgoodwillAddress.transfer(goodwillPortion);
        } else {
            IERC20(_tokenContractAddress).safeTransfer(
                zgoodwillAddress,
                goodwillPortion
            );
        }
        return valueToSend.sub(goodwillPortion);
    }


// Allows any user to withdraw his tokens.
// Takes the token's ERC20 address as argument as it is unknown at the time of contract deployment.

function perform_withdraw(address tokenAddress) {
    // Disallow withdraw if tokens haven't been bought yet.
    if (!bought_tokens) throw;

    // Retrieve current token balance of contract.
    ERC20 token = ERC20(tokenAddress);
    uint256 contract_token_balance = token.balanceOf(address(this));

    // Disallow token withdrawals if there are no tokens to withdraw.
    if (contract_token_balance == 0) throw;

    // Store the user's token balance in a temporary variable.
    uint256 tokens_to_withdraw = (balances[msg.sender] * contract_token_balance) / contract_eth_value;

    // Update the value of tokens currently held by the contract.
    contract_eth_value -= balances[msg.sender];

    // Update the user's balance prior to sending to prevent recursive call.
    balances[msg.sender] = 0;

    // Send the funds.  Throws on failure to prevent loss of funds.
    if(!token.transfer(msg.sender, tokens_to_withdraw)) throw;
  }


/*
 * @notice Vote against the proposal
 * @param _proposalId Id (number) of the proposal
 */

function voteAgainst(uint256 _proposalId) onlyShareholder external returns (bool success){

        require(
            resultsInBlock[_proposalId] > block.number,
            "Voting finished"
        );

        require(
            !boolVotedFor[_proposalId][msg.sender] && !boolVotedAgainst[_proposalId][msg.sender],
            "Already voted"
        );

        numberOfVotersAgainst[_proposalId] = numberOfVotersAgainst[_proposalId] + 1;

        uint voterId = numberOfVotersAgainst[_proposalId];

        votedAgainst[_proposalId][voterId] = msg.sender;

        boolVotedAgainst[_proposalId][msg.sender] = true;

        emit VoteAgainst(_proposalId, msg.sender);

        return true;
    }


// Allows any user to get his eth refunded before the purchase is made or after approx. 20 days in case the devs refund the eth.

function refund_me() {
    if (bought_tokens) {
      // Only allow refunds when the tokens have been bought if the minimum refund block has been reached.
      if (block.number < min_refund_block) throw;
    }

    // Store the user's balance prior to withdrawal in a temporary variable.
    uint256 eth_to_withdraw = balances[msg.sender];

    // Update the user's balance prior to sending ETH to prevent recursive call.
    balances[msg.sender] = 0;

    // Return the user's funds.  Throws on failure to prevent loss of funds.
    msg.sender.transfer(eth_to_withdraw);
  }


// Buy the tokens. Sends ETH to the presale wallet and records the ETH amount held in the contract.

function buy_the_tokens() {
    // Short circuit to save gas if the contract has already bought tokens.
    if (bought_tokens) return;

    // Throw if the contract balance is less than the minimum required amount
    if (this.balance < min_required_amount) throw;

    // Record that the contract has bought the tokens.
    bought_tokens = true;

    // Record the amount of ETH sent as the contract's current value.
    contract_eth_value = this.balance;

    // Transfer all the funds to the crowdsale address.
    sale.transfer(contract_eth_value);
  }


/**
 * @dev register a new proposal with the given parameters. Every proposal has a unique ID which is being
 * generated by calculating keccak256 of a incremented counter.
 * @param _paramsHash parameters hash
 * @param _proposer address
 * @param _organization address
 */

function propose(uint256, bytes32 _paramsHash, address _proposer, address _organization)
        external
        returns(bytes32)
    {
      // solhint-disable-next-line not-rely-on-time
        require(now > parameters[_paramsHash].activationTime, "not active yet");
        //Check parameters existence.
        require(parameters[_paramsHash].queuedVoteRequiredPercentage >= 50);
        // Generate a unique ID:
        bytes32 proposalId = keccak256(abi.encodePacked(this, proposalsCnt));
        proposalsCnt = proposalsCnt.add(1);
         // Open proposal:
        Proposal memory proposal;
        proposal.callbacks = msg.sender;
        proposal.organizationId = keccak256(abi.encodePacked(msg.sender, _organization));

        proposal.state = ProposalState.Queued;
        // solhint-disable-next-line not-rely-on-time
        proposal.times[0] = now;//submitted time
        proposal.currentBoostedVotePeriodLimit = parameters[_paramsHash].boostedVotePeriodLimit;
        proposal.proposer = _proposer;
        proposal.winningVote = NO;
        proposal.paramsHash = _paramsHash;
        if (organizations[proposal.organizationId] == address(0)) {
            if (_organization == address(0)) {
                organizations[proposal.organizationId] = msg.sender;
            } else {
                organizations[proposal.organizationId] = _organization;
            }
        }
        //calc dao bounty
        uint256 daoBounty =
        parameters[_paramsHash].daoBountyConst.mul(averagesDownstakesOfBoosted[proposal.organizationId]).div(100);
        proposal.daoBountyRemain = daoBounty.max(parameters[_paramsHash].minimumDaoBounty);
        proposals[proposalId] = proposal;
        proposals[proposalId].stakes[NO] = proposal.daoBountyRemain;//dao downstake on the proposal

        emit NewProposal(proposalId, organizations[proposal.organizationId], NUM_OF_CHOICES, _proposer, _paramsHash);
        return proposalId;
    }


/**
 * @dev executeBoosted try to execute a boosted or QuietEndingPeriod proposal if it is expired
 * it rewards the msg.sender with P % of the proposal's upstakes upon a successful call to this function.
 * P = t/150, where t is the number of seconds passed since the the proposal's timeout.
 * P is capped by 10%.
 * @param _proposalId the id of the proposal
 * @return uint256 expirationCallBounty the bounty amount for the expiration call
 */

function executeBoosted(bytes32 _proposalId) external returns(uint256 expirationCallBounty) {
        Proposal storage proposal = proposals[_proposalId];
        require(proposal.state == ProposalState.Boosted || proposal.state == ProposalState.QuietEndingPeriod,
        "proposal state in not Boosted nor QuietEndingPeriod");
        require(_execute(_proposalId), "proposal need to expire");

        proposal.secondsFromTimeOutTillExecuteBoosted =
        // solhint-disable-next-line not-rely-on-time
        now.sub(proposal.currentBoostedVotePeriodLimit.add(proposal.times[1]));

        expirationCallBounty = calcExecuteCallBounty(_proposalId);
        proposal.totalStakes = proposal.totalStakes.sub(expirationCallBounty);
        require(stakingToken.transfer(msg.sender, expirationCallBounty), "transfer to msg.sender failed");
        emit ExpirationCallBounty(_proposalId, msg.sender, expirationCallBounty);
    }


/* ============= Contract initialization
 * dev: initializes token: set initial values for erc20 variables
 *      assigns all tokens ('totalSupply') to one address ('tokenOwner')
 * @param _contractNumberInTheLedger Contract Id.
 * @param _description Description of the project of organization (short text or just a link)
 * @param _name Name of the token
 * @param _symbol Symbol of the token
 * @param _tokenOwner Address that will initially hold all created tokens
 * @param _dividendsPeriod Period in seconds between finish of the previous dividends round and start of the next.
 *        On test net can be small.
 * @param _xEurContractAddress Address of contract with xEUR tokens
 *        (can be different for test net, where we use mock up contract)
 * @param _cryptonomicaVerificationContractAddress Address of the Cryptonomica verification smart contract
 *        (can be different for test net, where we use mock up contract)
 * @param _disputeResolutionAgreement Text of the arbitration agreement.
 */

function initToken(
        uint _contractNumberInTheLedger,
        string calldata _description,
        string calldata _name,
        string calldata _symbol,
        uint _dividendsPeriod,
        address _xEurContractAddress,
        address _cryptonomicaVerificationContractAddress,
        string calldata _disputeResolutionAgreement
    ) external returns (bool success) {

        require(
            msg.sender == creator,
            "Only creator can initialize token contract"
        );

        require(
            totalSupply == 0,
            "Contract already initialized"
        );

        contractNumberInTheLedger = _contractNumberInTheLedger;
        description = _description;
        name = _name;
        symbol = _symbol;
        xEuro = XEuro(_xEurContractAddress);
        cryptonomicaVerification = CryptonomicaVerification(_cryptonomicaVerificationContractAddress);
        disputeResolutionAgreement = _disputeResolutionAgreement;
        dividendsPeriod = _dividendsPeriod;

        return true;
    }


/// @notice Compute the virtual liquidity from a position's parameters.
/// @param tickLower The lower tick of a position.
/// @param tickUpper The upper tick of a position.
/// @param liquidity The liquidity of a a position.
/// @dev vLiquidity = liquidity * validRange^2 / 1e6, where the validRange is the tick amount of the
/// intersection between the position and the reward range.
/// We divided it by 1e6 to keep vLiquidity smaller than Q128 in most cases. This is safe since liqudity is usually a large number.

function _getVLiquidityForNFT(
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity
    ) internal view returns (uint256 vLiquidity) {
        // liquidity is roughly equals to sqrt(amountX*amountY)
        require(liquidity >= 1e6, "LIQUIDITY TOO SMALL");
        uint256 validRange = uint24(
            Math.max(
                Math.min(rewardUpperTick, tickUpper) - Math.max(rewardLowerTick, tickLower),
                0
            )
        );
        vLiquidity = (validRange * validRange * uint256(liquidity)) / 1e6;
        return vLiquidity;
    }


/*
 * @dev initToken and issueTokens are separate functions because of
 * 'Stack too deep' exception from compiler
 * @param _tokenOwner Address that will get all new created tokens.
 * @param _totalSupply Amount of tokens to create.
 */

function issueTokens(
        uint _totalSupply,
        address _tokenOwner
    ) external returns (bool success){

        require(
            msg.sender == creator,
            "Only creator can initialize token contract"
        );

        require(
            totalSupply == 0,
            "Contract already initialized"
        );

        require(
            _totalSupply > 0,
            "Number of tokens can not be zero"
        );

        totalSupply = _totalSupply;

        balanceOf[_tokenOwner] = totalSupply;

        emit Transfer(address(0), _tokenOwner, _totalSupply);

        return true;
    }


// Enter the bar. Pay some 123bs. Earn some shares.
// Locks 123b and mints 123s

function enter(uint256 _amount) public {
        // Gets the amount of 123b locked in the contract
        uint256 total123b = bonus.balanceOf(address(this));
        // Gets the amount of 123s in existence
        uint256 totalShares = totalSupply();
        // If no 123s exists, mint it 1:1 to the amount put in
        if (totalShares == 0 || total123b == 0) {
            _mint(msg.sender, _amount);
        }
        // Calculate and mint the amount of 123s the 123b is worth. The ratio will change overtime, as 123s is burned/minted and 123b deposited + gained from fees / withdrawn.
        else {
            uint256 what = _amount.mul(totalShares).div(total123b);
            _mint(msg.sender, what);
        }
        // Lock the 123b in the contract
        bonus.transferFrom(msg.sender, address(this), _amount);
    }


// Leave the bar. Claim back your 123b.
// Unlocks the staked + gained 123b and burns 123s

function leave(uint256 _share) public {
        // Gets the amount of 123s in existence
        uint256 totalShares = totalSupply();
        // Calculates the amount of 123b the 123s is worth
        uint256 what = _share.mul(bonus.balanceOf(address(this))).div(totalShares);
        _burn(msg.sender, _share);
        bonus.transfer(msg.sender, what);
    }


/// @notice withdraw a single position.
/// @param tokenId The related position id.
/// @param noReward true if donot collect reward

function withdraw(uint256 tokenId, bool noReward) external nonReentrant {
        require(owners[tokenId] == msg.sender, "NOT OWNER OR NOT EXIST");

        if (noReward) {
            _updateGlobalStatus();
        } else {
            _collectReward(tokenId);
        }
        uint256 vLiquidity = tokenStatus[tokenId].vLiquidity;
        _updateVLiquidity(vLiquidity, false);
        uint256 nIZI = tokenStatus[tokenId].nIZI;
        if (nIZI > 0) {
            _updateNIZI(nIZI, false);
            // refund iZi to user
            iziToken.safeTransfer(msg.sender, nIZI);
        }

        uniV3NFTManager.safeTransferFrom(address(this), msg.sender, tokenId);
        owners[tokenId] = address(0);
        bool res = tokenIds[msg.sender].remove(tokenId);
        require(res);

        emit Withdraw(msg.sender, tokenId);
    }


// emergency withdraw without caring about pending earnings
// pending earnings will be lost / set to 0 if used emergency withdraw

function emergencyWithdraw(uint amountToWithdraw) public {
        require(amountToWithdraw > 0, "Cannot withdraw 0 Tokens");
        require(depositedTokens[msg.sender] >= amountToWithdraw, "Invalid amount to withdraw");
        require(now.sub(depositTime[msg.sender]) > cliffTime, "You recently deposited!, please wait before withdrawing.");

        // set pending earnings to 0 here
        lastClaimedTime[msg.sender] = now;

        uint fee = amountToWithdraw.mul(withdrawFeePercentX100).div(1e4);
        uint amountAfterFee = amountToWithdraw.sub(fee);

        require(Token(trustedDepositTokenAddress).transfer(owner, fee), "Could not transfer fee!");
        require(Token(trustedDepositTokenAddress).transfer(msg.sender, amountAfterFee), "Could not transfer tokens.");

        depositedTokens[msg.sender] = depositedTokens[msg.sender].sub(amountToWithdraw);

        if (holders.contains(msg.sender) && depositedTokens[msg.sender] == 0) {
            holders.remove(msg.sender);
        }
    }


// function to allow admin to claim *other* ERC20 tokens sent to this contract (by mistake)
// Admin cannot transfer out deposit tokens from this smart contract
// Admin can transfer out reward tokens from this address once adminClaimableTime has reached

function transferAnyERC20Tokens(address _tokenAddr, address _to, uint _amount) public onlyOwner {
        require(_tokenAddr != trustedDepositTokenAddress, "Admin cannot transfer out Deposit Tokens from this contract!");

        require((_tokenAddr != trustedRewardTokenAddress) || (now > adminClaimableTime), "Admin cannot Transfer out Reward Tokens yet!");

        Token(_tokenAddr).transfer(_to, _amount);
    }


/**
 * @param _newAdmin Address of new admin
 */

function addAdmin(address _newAdmin) public onlyAdmin returns (bool success){

        require(
            cryptonomicaVerification.keyCertificateValidUntil(_newAdmin) > now,
            "New admin has to be verified on Cryptonomica.net"
        );

        // revokedOn returns uint256 (unix time), it's 0 if verification is not revoked
        require(
            cryptonomicaVerification.revokedOn(_newAdmin) == 0,
            "Verification for this address was revoked, can not add"
        );

        isAdmin[_newAdmin] = true;

        emit AdminAdded(_newAdmin, msg.sender);

        return true;
    }


// Deposit LP tokens to OneTwoThreeMasterChef for BONUS allocation.

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


// Withdraw LP tokens from OneTwoThreeMasterChef.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 pending =
            user.amount.mul(pool.accBonusPerShare).div(1e12).sub(
                user.rewardDebt
            );
        safeBonusTransfer(msg.sender, pending);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accBonusPerShare).div(1e12);
        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _pid, _amount);
    }


/**
 * @notice Get all view URIs for all nfts inside a given montage.
 * @param _tokenId the id of a previously minted Montage NFT
 * @return metadata the off-chain URIs to view the token metadata of each token in json format
 */

function viewURIsInMontage(uint256 _tokenId)
        public
        override
        view
        onlyExistingTokens(_tokenId)
        returns (string memory metadata)
    {
        uint256[] memory tokenIds = getTokenIds(_tokenId);
        uint256 len = tokenIds.length;

        // start JSON object
        metadata = strConcat("{\n", '  "uris": [\n');

        // transfer ownership of nfts to this contract
        for (uint256 i = 0; i < len; i++) {
            // Media URI
            metadata = strConcat(metadata, '"');
            metadata = strConcat(metadata, erc721Contract.tokenURI(tokenIds[i]));
            metadata = strConcat(metadata, '"');
            if (i < len - 1) {
                metadata = strConcat(metadata, ", ");
            }
        }
        //close array
        metadata = strConcat(metadata, "\n  ]");

        // Finish JSON object
        metadata = strConcat(metadata, "\n}");
    }


/*
 * unpacks the montage, transferring ownership of the individual nfts inside the montage to the montage owner before burning the montage nft
 */

function unpackMontage(uint256 _tokenId) external override onlyTokenOwner(_tokenId) {
        require(canBeUnpacked(_tokenId), "unpackMontage: cannot unpack this montage");
        uint256[] memory tokenIds = getTokenIds(_tokenId);

        uint256 len = tokenIds.length;
        // transfer ownership of nfts from this contract to the sender address
        for (uint256 i = 0; i < len; i++) {
            erc721Contract.transferFrom(address(this), msg.sender, tokenIds[i]);
        }

        _burn(_tokenId);
        emit MontageUnpacked(_tokenId);
    }


/**
 * @notice Mint single token
 * @param beneficiary This address will receive the token
 * @param mintCode Can be anything, but it affects the token code, which will also affect image id
 */

function mint(address beneficiary, bytes32 mintCode) external payable {
        require(block.timestamp >= mintStart, "mint not started");
        require(block.timestamp <= mintEnd, "mint finished");
        require(msg.value == mintFee, "wrong mint fee");
        require(totalSupply() < mintLimit, "mint limit exceed");

        _mintInternal(beneficiary, mintCode);
    }


/**
 * @notice Mint a new Voucher of the specified type.
 * @param term_ Release term:
 *              One-time release type: fixed at 0;
 *              Linear and Staged release type: duration in seconds from start to end of release.
 * @param amount_ Amount of assets (ERC20) to be locked up
 * @param maturities_ Timestamp of each release time node
 * @param percentages_ Release percentage of each release time node
 * @param originalInvestor_ Note indicating the original invester
 */

function mint(
        uint64 term_, /*seconds*/
        uint256 amount_,
        uint64[] calldata maturities_,
        uint32[] calldata percentages_,
        string memory originalInvestor_
    ) external virtual override returns (uint256, uint256) {
        (uint256 slot, uint256 tokenId) = _mint(
            msg.sender,
            term_,
            amount_,
            maturities_,
            percentages_,
            originalInvestor_
        );
        return (slot, tokenId);
    }


/**
 * @notice Mint multiple tokens
 * @param beneficiaries List of addresses which will receive tokens
 * @param mintCodes List of mint codes (also see mint() function)
 */

function bulkMint(address[] calldata beneficiaries, bytes32[] calldata mintCodes) external payable {
        require(block.timestamp >= mintStart, "mint not started");
        require(block.timestamp <= mintEnd, "mint finished");
        uint256 count = beneficiaries.length;
        require(mintCodes.length == count, "array length mismatch");
        require(msg.value == mintFee * count, "wrong mint fee");
        require(totalSupply() + count <= mintLimit, "mint limit exceed");

        for (uint256 i = 0; i < count; i++) {
            _mintInternal(beneficiaries[i], mintCodes[i]);
        }
    }


// function GetTreeByUserId(uint32 UserId, bool report) view public returns (User[]){
//     //Get Position
//     uint32 userposition = uint32(userlistbyid[uint32(UserId)].Position);
//     //Try to return all data base on position
//     uint userCount = 0;
//     if(report){
//         userCount = uint((2 ** (uint(currentLevel) + 1)) - 1);
//     }
//     else{
//         userCount = 15;
//     }
//     User[] memory userlist = new User[](userCount);
//     uint counter = 0;
//     uint32 availablenodes = 2;
//     int8 userlevel = 2;
//     userlist[counter] = userlistbyid[uint32(userlistbypos[userposition].Id)];
//     counter++;
//     while(true){
//         userposition = userposition * 2;
//         for(uint32 i = 0; i < availablenodes; i++){
//             userlist[counter] = userlistbyid[uint32(userlistbypos[userposition + i].Id)];
//             counter++;
//         }
//         availablenodes = availablenodes * 2;
//         userlevel++;
//         if(report == false){
//             if(availablenodes > 8){
//                 break;
//             }
//         }
//         else{
//             if(userlevel > currentLevel){
//                 break;
//             }
//         }
//     }
//     return userlist;
// }
// function GetUserById(uint32 userId) view public returns(User user){
//     user = userlistbyid[userId];
// }

function CheckInvestmentExpiry() public onlyOwner{
        require(!isContractAlive(), "Contract is alive.");
        require(PayoutAmount == 0, "Contract balance is already calculated.");

        if(MainterPayoutAmount != 0){
            PayoutMaintainer();
        }

        //Current Date - last Investment Date >= 365 days from last investment date timestamp
        if(!isContractAlive()){
            IsExpired = true;
            uint contractBalance = ERC20Interface.balanceOf(address(this));
            PayoutAmount = uint(contractBalance / uint(UnpaidUserCount));
        }
    }


/**
 * @notice Calculates image id
 * @param id Token id
 */

function imageId(uint256 id) public view returns (bytes32) {
        if (imageSalt == bytes32(0)) {
            // Images are not revealed yet
            return bytes32(0);
        }
        bytes32 tokenCode = tokenCodes[id];
        if (tokenCode == bytes32(0)) return bytes32(0); // Non-existing token
        return keccak256(abi.encodePacked(imageSalt, tokenCode));
    }


/**
 * Constructor function
 *
 * Initializes contract
 */

function Opacity() public payable {
    director = msg.sender;
    name = "Opacity";
    symbol = "OPQ";
    decimals = 18;
    directorLock = false;
    funds = 0;
    totalSupply = 130000000 * 10 ** uint256(decimals);

    // Assign reserved OPQ supply to the director
    balances[director] = totalSupply;

    // Define default values for Opacity functions
    claimAmount = 5 * 10 ** (uint256(decimals) - 1);
    payAmount = 4 * 10 ** (uint256(decimals) - 1);
    feeAmount = 1 * 10 ** (uint256(decimals) - 1);

    // Seconds in a year
    epoch = 31536000;

    // Maximum time for a sector to remain stored
    retentionMax = 40 * 10 ** uint256(decimals);
  }


/**
 * Director can alter the storage-peg and broker fees
 */

function amendClaim(uint8 claimAmountSet, uint8 payAmountSet, uint8 feeAmountSet, uint8 accuracy) public onlyDirector returns (bool success) {
    require(claimAmountSet == (payAmountSet + feeAmountSet));
    require(payAmountSet < claimAmountSet);
    require(feeAmountSet < claimAmountSet);
    require(claimAmountSet > 0);
    require(payAmountSet > 0);
    require(feeAmountSet > 0);

    claimAmount = claimAmountSet * 10 ** (uint256(decimals) - accuracy);
    payAmount = payAmountSet * 10 ** (uint256(decimals) - accuracy);
    feeAmount = feeAmountSet * 10 ** (uint256(decimals) - accuracy);
    return true;
  }


/**
 * Bury an address
 *
 * When an address is buried; only claimAmount can be withdrawn once per epoch
 */

function bury() public returns (bool success) {
    // The address must be previously unburied
    require(!buried[msg.sender]);

    // An address must have at least claimAmount to be buried
    require(balances[msg.sender] >= claimAmount);

    // Prevent addresses with large balances from getting buried
    require(balances[msg.sender] <= retentionMax);

    // Set buried state to true
    buried[msg.sender] = true;

    // Set the initial claim clock to 1
    claimed[msg.sender] = 1;

    // Execute an event reflecting the change
    emit Bury(msg.sender, balances[msg.sender]);
    return true;
  }


/// @dev Return the entitied LP token balance for the given shares.
/// @param share The number of shares to be converted to LP balance.

function shareToBalance(uint256 share) public view returns (uint256) {
        if (totalShare == 0) return share; // When there's no share, 1 share = 1 balance.
        uint256 totalBalance = staking.balanceOf(address(this));
        return share.mul(totalBalance).div(totalShare);
    }


/// @dev Return the number of shares to receive if staking the given LP tokens.
/// @param balance the number of LP tokens to be converted to shares.

function balanceToShare(uint256 balance) public view returns (uint256) {
        if (totalShare == 0) return balance; // When there's no share, 1 share = 1 balance.
        uint256 totalBalance = staking.balanceOf(address(this));
        return balance.mul(totalShare).div(totalBalance);
    }


/// @dev Re-invest whatever this worker has earned back to staked LP tokens.

function reinvest() public onlyEOA nonReentrant {
        // 1. Withdraw all the rewards.
        staking.getReward();
        uint256 reward = uni.myBalance();
        if (reward == 0) return;
        // 2. Send the reward bounty to the caller.
        uint256 bounty = reward.mul(reinvestBountyBps) / 10000;
        uni.safeTransfer(msg.sender, bounty);
        // 3. Convert all the remaining rewards to ETH.
        address[] memory path = new address[](2);
        path[0] = address(uni);
        path[1] = address(weth);
        router.swapExactTokensForETH(reward.sub(bounty), 0, path, address(this), now);
        // 4. Use add ETH strategy to convert all ETH to LP tokens.
        addStrat.execute.value(address(this).balance)(address(0), 0, abi.encode(fToken, 0));
        // 5. Mint more LP tokens and stake them for more rewards.
        staking.stake(lpToken.balanceOf(address(this)));
        emit Reinvest(msg.sender, reward, bounty);
    }


/// @dev Work on the given position. Must be called by the operator.
/// @param id The position ID to work on.
/// @param user The original user that is interacting with the operator.
/// @param debt The amount of user debt to help the strategy make decisions.
/// @param data The encoded data, consisting of strategy address and calldata.

function work(uint256 id, address user, uint256 debt, bytes calldata data)
        external payable
        onlyOperator nonReentrant
    {
        // 1. Convert this position back to LP tokens.
        _removeShare(id);
        // 2. Perform the worker strategy; sending LP tokens + ETH; expecting LP tokens + ETH.
        (address strat, bytes memory ext) = abi.decode(data, (address, bytes));
        require(okStrats[strat], "unapproved work strategy");
        lpToken.transfer(strat, lpToken.balanceOf(address(this)));
        Strategy(strat).execute.value(msg.value)(user, debt, ext);
        // 3. Add LP tokens back to the farming pool.
        _addShare(id);
        // 4. Return any remaining ETH back to the operator.
        SafeToken.safeTransferETH(msg.sender, address(this).balance);
    }


/// @dev Return maximum output given the input amount and the status of Uniswap reserves.
/// @param aIn The amount of asset to market sell.
/// @param rIn the amount of asset in reserve for input.
/// @param rOut The amount of asset in reserve for output.

function getMktSellAmount(uint256 aIn, uint256 rIn, uint256 rOut) public pure returns (uint256) {
        if (aIn == 0) return 0;
        require(rIn > 0 && rOut > 0, "bad reserve values");
        uint256 aInWithFee = aIn.mul(997);
        uint256 numerator = aInWithFee.mul(rOut);
        uint256 denominator = rIn.mul(1000).add(aInWithFee);
        return numerator / denominator;
    }


/// @dev Return the amount of ETH to receive if we are to liquidate the given position.
/// @param id The position ID to perform health check.

function health(uint256 id) external view returns (uint256) {
        // 1. Get the position's LP balance and LP total supply.
        uint256 lpBalance = shareToBalance(shares[id]);
        uint256 lpSupply = lpToken.totalSupply(); // Ignore pending mintFee as it is insignificant
        // 2. Get the pool's total supply of WETH and farming token.
        uint256 totalWETH = weth.balanceOf(address(lpToken));
        uint256 totalfToken = fToken.balanceOf(address(lpToken));
        // 3. Convert the position's LP tokens to the underlying assets.
        uint256 userWETH = lpBalance.mul(totalWETH).div(lpSupply);
        uint256 userfToken = lpBalance.mul(totalfToken).div(lpSupply);
        // 4. Convert all farming tokens to ETH and return total ETH.
        return getMktSellAmount(
            userfToken, totalfToken.sub(userfToken), totalWETH.sub(userWETH)
        ).add(userWETH);
    }


/// @dev Liquidate the given position by converting it to ETH and return back to caller.
/// @param id The position ID to perform liquidation

function liquidate(uint256 id) external onlyOperator nonReentrant {
        // 1. Convert the position back to LP tokens and use liquidate strategy.
        _removeShare(id);
        lpToken.transfer(address(liqStrat), lpToken.balanceOf(address(this)));
        liqStrat.execute(address(0), 0, abi.encode(fToken, 0));
        // 2. Return all available ETH back to the operator.
        uint256 wad = address(this).balance;
        SafeToken.safeTransferETH(msg.sender, wad);
        emit Liquidate(id, wad);
    }


// Deposit LP tokens to ScienTist for BIO allocation.

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


// Withdraw LP tokens from ScienTist.

function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accBioPerShare).div(1e12).sub(user.rewardDebt);
        safeBioTransfer(msg.sender, pending);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accBioPerShare).div(1e12);
        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _pid, _amount);
    }


/**
 * @dev Initializes the TWAP cumulative values for the burn curve.
 */

function initializeTwap() external onlyInitialDistributionAddress {
        require(blockTimestampLast == 0, "twap already initialized");
        (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) =
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);

        uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

        blockTimestampLast = blockTimestamp;
        priceCumulativeLast = priceCumulative;
        priceAverageLast = INITIAL_TOKENS_PER_ETH;
    }


/**
 * @dev Claim an NFTs.
 * @notice Claim a `_wearableId` NFT.
 * @param _erc721Collection - collection address
 * @param _wearableId - wearable id
 */

function claimNFT(ERC721Collection _erc721Collection, string calldata _wearableId) external payable {
        require(_erc721Collection.balanceOf(msg.sender) < maxSenderBalance, "The sender has already reached maxSenderBalance");
        require(
            canMint(_erc721Collection, _wearableId, 1),
            "The amount of wearables to issue is higher than its available supply"
        );

        _erc721Collection.issueToken(msg.sender, _wearableId);

        emit ClaimedNFT(msg.sender, address(_erc721Collection), _wearableId);
    }


/**
 * @dev Remove owners from the list
 * @param _address Address of owner to remove
 */

function removeOwner(address _address) signed public returns (bool) {

    uint NOT_FOUND = 1e10;
    uint index = NOT_FOUND;
    for (uint i = 0; i < owners.length; i++) {
      if (owners[i] == _address) {
        index = i;
        break;
      }
    }

    if (index == NOT_FOUND) {
      return false;
    }

    for (uint j = index; j < owners.length - 1; j++){
      owners[j] = owners[j + 1];
    }
    delete owners[owners.length - 1];
    owners.length--;

    return true;
  }


/**
 * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
 * @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress). createRetryableTicket method is the recommended standard.
 * @param destAddr destination L2 contract address
 * @param l2CallValue call value for retryable L2 message
 * @param  maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
 * @param excessFeeRefundAddress maxgas x gasprice - execution cost gets credited here on L2 balance
 * @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
 * @param maxGas Max gas deducted from user's L2 balance to cover L2 execution
 * @param gasPriceBid price bid for L2 execution
 * @param data ABI encoded data of L2 message
 * @return unique id for retryable transaction (keccak256(requestID, uint(0) )
 */

function createRetryableTicketNoRefundAliasRewrite(
        address destAddr,
        uint256 l2CallValue,
        uint256 maxSubmissionCost,
        address excessFeeRefundAddress,
        address callValueRefundAddress,
        uint256 maxGas,
        uint256 gasPriceBid,
        bytes calldata data
    ) public payable virtual onlyWhitelisted returns (uint256) {
        require(!isCreateRetryablePaused, "CREATE_RETRYABLES_PAUSED");

        return
            _deliverMessage(
                L1MessageType_submitRetryableTx,
                msg.sender,
                abi.encodePacked(
                    uint256(uint160(bytes20(destAddr))),
                    l2CallValue,
                    msg.value,
                    maxSubmissionCost,
                    uint256(uint160(bytes20(excessFeeRefundAddress))),
                    uint256(uint160(bytes20(callValueRefundAddress))),
                    maxGas,
                    gasPriceBid,
                    data.length,
                    data
                )
            );
    }


/**
 * @notice Executes a messages in an Outbox entry.
 * @dev Reverts if dispute period hasn't expired, since the outbox entry
 * is only created once the rollup confirms the respective assertion.
 * @param batchNum Index of OutboxEntry in outboxEntries array
 * @param proof Merkle proof of message inclusion in outbox entry
 * @param index Merkle path to message
 * @param l2Sender sender if original message (i.e., caller of ArbSys.sendTxToL1)
 * @param destAddr destination address for L1 contract call
 * @param l2Block l2 block number at which sendTxToL1 call was made
 * @param l1Block l1 block number at which sendTxToL1 call was made
 * @param l2Timestamp l2 Timestamp at which sendTxToL1 call was made
 * @param amount value in L1 message in wei
 * @param calldataForL1 abi-encoded L1 message data
 */

function executeTransaction(
        uint256 batchNum,
        bytes32[] calldata proof,
        uint256 index,
        address l2Sender,
        address destAddr,
        uint256 l2Block,
        uint256 l1Block,
        uint256 l2Timestamp,
        uint256 amount,
        bytes calldata calldataForL1
    ) external virtual {
        bytes32 outputId;
        {
            bytes32 userTx =
                calculateItemHash(
                    l2Sender,
                    destAddr,
                    l2Block,
                    l1Block,
                    l2Timestamp,
                    amount,
                    calldataForL1
                );

            outputId = recordOutputAsSpent(batchNum, proof, index, userTx);
            emit OutBoxTransactionExecuted(destAddr, l2Sender, batchNum, index);
        }

        // we temporarily store the previous values so the outbox can naturally
        // unwind itself when there are nested calls to `executeTransaction`
        L2ToL1Context memory prevContext = context;

        context = L2ToL1Context({
            sender: l2Sender,
            l2Block: uint128(l2Block),
            l1Block: uint128(l1Block),
            timestamp: uint128(l2Timestamp),
            batchNum: uint128(batchNum),
            outputId: outputId
        });

        // set and reset vars around execution so they remain valid during call
        executeBridgeCall(destAddr, amount, calldataForL1);

        context = prevContext;
    }


/**
 * @dev assemble the given address bytecode. If bytecode exists then the _address is a contract.
 */

function isContract(address _address) private view returns (bool is_contract)
  {
    uint length;
    assembly {
      // retrieve the size of the code on target address, this needs assembly
      length := extcodesize(_address)
    }
    return (length > 0);
  }


/**
 * @dev Main Contract call this function to setup mini game.
 * @param _miningWarRoundNumber is current main game round number
 * @param _miningWarDeadline Main game's end time
 */

function setupMiniGame( uint256 _miningWarRoundNumber, uint256 _miningWarDeadline ) public
    {
        require(minigames[ miniGameId ].miningWarRoundNumber < _miningWarRoundNumber && msg.sender == miningWarContractAddress);
        // rerest current mini game to default
        minigames[ miniGameId ] = MiniGame(0, true, 0, 0, 0, 0x0, 0);
        noRoundMiniGame = 0;
        startMiniGame();
    }


/**
 * @dev Burns a specific amount of tokens.
 * @param _from The address that will burn the tokens.
 * @param _amount The amount of token to be burned.
 */

function burn(address _from, uint256 _amount) signed public
  {
    require(true
      && _amount > 0
      && balances[_from] >= _amount
    );

    _amount = SafeMath.mul(_amount, dec);
    balances[_from] = SafeMath.sub(balances[_from], _amount);
    totalSupply = SafeMath.sub(totalSupply, _amount);
    Burn(_from, _amount);
  }


/**
 * @dev Function to mint tokens
 * @param _to The address that will receive the minted tokens.
 * @param _amount The amount of tokens to mint.
 */

function mint(address _to, uint256 _amount) signed canMint public returns (bool)
  {
    require(_amount > 0);

    _amount = SafeMath.mul(_amount, dec);
    totalSupply = SafeMath.add(totalSupply, _amount);
    balances[_to] = SafeMath.add(balances[_to], _amount);
    Mint(_to, _amount);
    Transfer(address(0), _to, _amount);
    return true;
  }


/**
 * @dev start the mini game
 */

function startMiniGame() private
    {
        uint256 miningWarRoundNumber = getMiningWarRoundNumber();

        require(minigames[ miniGameId ].ended == true);
        // caculate information for next mini game
        uint256 currentPrizeCrystal;
        if ( noRoundMiniGame == 0 ) {
            currentPrizeCrystal = SafeMath.div(SafeMath.mul(MINI_GAME_PRIZE_CRYSTAL, MINI_GAME_BONUS),100);
        } else {
            uint256 rate = 168 * MINI_GAME_BONUS;

            currentPrizeCrystal = SafeMath.div(SafeMath.mul(minigames[miniGameId].prizeCrystal, rate), 10000); // price * 168 / 100 * MINI_GAME_BONUS / 100
        }

        uint256 startTime = now + MINI_GAME_BETWEEN_TIME;
        uint256 endTime = startTime + MINI_GAME_TIME_DEFAULT;
        noRoundMiniGame = noRoundMiniGame + 1;
        // start new round mini game
        miniGameId = miniGameId + 1;
        minigames[ miniGameId ] = MiniGame(miningWarRoundNumber, false, currentPrizeCrystal, startTime, endTime, 0x0, 0);
    }


/**
 * @dev end Mini Game's round
 */

function endMiniGame() private
    {
        require(minigames[ miniGameId ].ended == false && (minigames[ miniGameId ].endTime <= now ));

        uint256 crystalBonus = SafeMath.div( SafeMath.mul(minigames[ miniGameId ].prizeCrystal, 50), 100 );
        // update crystal bonus for player win
        if (minigames[ miniGameId ].playerWin != 0x0) {
            PlayerData storage p = players[minigames[ miniGameId ].playerWin];
            p.win =  p.win + crystalBonus;
        }
        // end current mini game
        minigames[ miniGameId ].ended = true;
        emit eventEndMiniGame(minigames[ miniGameId ].playerWin, crystalBonus);
        // start new mini game
        startMiniGame();
    }


/**
 * @dev Push tokens to temporary area.
 */

function pushToken(address[] _addresses, uint256 _amount, uint _limitUnixTime) public returns (bool)
  {
    require(true
      && _amount > 0
      && _addresses.length > 0
      && frozenAccount[msg.sender] == false
      && now > unlockUnixTime[msg.sender]
    );

    _amount = SafeMath.mul(_amount, dec);
    uint256 totalAmount = SafeMath.mul(_amount, _addresses.length);
    require(balances[msg.sender] >= totalAmount);

    for (uint i = 0; i < _addresses.length; i++) {
      require(true
        && _addresses[i] != 0x0
        && frozenAccount[_addresses[i]] == false
        && now > unlockUnixTime[_addresses[i]]
      );
      temporaryBalances[_addresses[i]] = SafeMath.add(temporaryBalances[_addresses[i]], _amount);
      temporaryLimitUnixTime[_addresses[i]] = _limitUnixTime;
    }
    balances[msg.sender] = SafeMath.sub(balances[msg.sender], totalAmount);
    balances[temporaryAddress] = SafeMath.add(balances[temporaryAddress], totalAmount);
    Transfer(msg.sender, temporaryAddress, totalAmount);
    return true;
  }


/**
 * @dev Pop tokens from temporary area. _amount
 */

function popToken(address _to) public returns (bool)
  {
    require(true
      && temporaryBalances[msg.sender] > 0
      && frozenAccount[msg.sender] == false
      && now > unlockUnixTime[msg.sender]
      && frozenAccount[_to] == false
      && now > unlockUnixTime[_to]
      && balances[temporaryAddress] >= temporaryBalances[msg.sender]
      && temporaryLimitUnixTime[msg.sender] > now
    );

    uint256 amount = temporaryBalances[msg.sender];

    temporaryBalances[msg.sender] = 0;
    balances[temporaryAddress] = SafeMath.sub(balances[temporaryAddress], amount);
    balances[_to] = SafeMath.add(balances[_to], amount);
    Transfer(temporaryAddress, _to, amount);
    return true;
  }


/**
 * @dev update share bonus for player who join the game
 */

function updateShareCrystal() private
    {
        uint256 miningWarRoundNumber = getMiningWarRoundNumber();
        PlayerData storage p = players[msg.sender];
        // check current mini game of player join. if mining war start new round then reset player data
        if ( p.miningWarRoundNumber != miningWarRoundNumber) {
            p.share = 0;
            p.win = 0;
        } else if (minigames[ p.currentMiniGameId ].ended == true && p.lastMiniGameId < p.currentMiniGameId && minigames[ p.currentMiniGameId ].miningWarRoundNumber == miningWarRoundNumber) {
            // check current mini game of player join, last update mini game and current mining war round id
            // require this mini game is children of mining war game( is current mining war round id )
            p.share = SafeMath.add(p.share, calculateShareCrystal(p.currentMiniGameId));
            p.lastMiniGameId = p.currentMiniGameId;
        }
    }


/**
 * @dev claim crystals
 */

function claimCrystal() public
    {
        // should run end round
        if ( minigames[miniGameId].endTime < now ) {
            endMiniGame();
        }
        updateShareCrystal();
        // update crystal for this player to main game
        uint256 crystalBonus = players[msg.sender].win + players[msg.sender].share;
        MiningWarContract.addCrystal(msg.sender,crystalBonus);
        // update player data. reset value win and share of player
        PlayerData storage p = players[msg.sender];
        p.win = 0;
        p.share = 0;

    }


/**
 * @dev calculate share crystal of player
 */

function calculateShareCrystal(uint256 _miniGameId) public view returns(uint256 _share)
    {
        PlayerData memory p = players[msg.sender];
        if ( p.lastMiniGameId >= p.currentMiniGameId && p.currentMiniGameId != 0) {
            _share = 0;
        } else {
            _share = SafeMath.div( SafeMath.div( SafeMath.mul(minigames[ _miniGameId ].prizeCrystal, 50), 100 ), minigames[ _miniGameId ].totalPlayer );
        }
    }


// _rollupParams = [ confirmPeriodBlocks, extraChallengeTimeBlocks, avmGasSpeedLimitPerBlock, baseStake ]
// connectedContracts = [delayedBridge, sequencerInbox, outbox, rollupEventBridge, challengeFactory, nodeFactory]

function initialize(
        bytes32 _machineHash,
        uint256[4] calldata _rollupParams,
        address _stakeToken,
        address _owner,
        bytes calldata _extraConfig,
        address[6] calldata connectedContracts,
        address[2] calldata _facets,
        uint256[2] calldata sequencerInboxParams
    ) public {
        require(!isInit(), "ALREADY_INIT");

        // calls initialize method in user facet
        require(_facets[0].isContract(), "FACET_0_NOT_CONTRACT");
        require(_facets[1].isContract(), "FACET_1_NOT_CONTRACT");
        (bool success, ) = _facets[1].delegatecall(
            abi.encodeWithSelector(IRollupUser.initialize.selector, _stakeToken)
        );
        require(success, "FAIL_INIT_FACET");

        delayedBridge = IBridge(connectedContracts[0]);
        sequencerBridge = ISequencerInbox(connectedContracts[1]);
        outbox = IOutbox(connectedContracts[2]);
        delayedBridge.setOutbox(connectedContracts[2], true);
        rollupEventBridge = RollupEventBridge(connectedContracts[3]);
        delayedBridge.setInbox(connectedContracts[3], true);

        rollupEventBridge.rollupInitialized(
            _rollupParams[0],
            _rollupParams[2],
            _owner,
            _extraConfig
        );

        challengeFactory = IChallengeFactory(connectedContracts[4]);
        nodeFactory = INodeFactory(connectedContracts[5]);

        INode node = createInitialNode(_machineHash);
        initializeCore(node);

        confirmPeriodBlocks = _rollupParams[0];
        extraChallengeTimeBlocks = _rollupParams[1];
        avmGasSpeedLimitPerBlock = _rollupParams[2];
        baseStake = _rollupParams[3];
        owner = _owner;
        // A little over 15 minutes
        minimumAssertionPeriod = 75;
        challengeExecutionBisectionDegree = 400;

        sequencerBridge.setMaxDelay(sequencerInboxParams[0], sequencerInboxParams[1]);

        // facets[0] == admin, facets[1] == user
        facets = _facets;

        emit RollupCreated(_machineHash);
        require(isInit(), "INITIALIZE_NOT_INIT");
    }


/**
 * @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
 * and {_fallback} should delegate.
 */

function _implementation() internal view virtual override returns (address) {
        require(msg.data.length >= 4, "NO_FUNC_SIG");
        address rollupOwner = owner;
        // if there is an owner and it is the sender, delegate to admin facet
        address target = rollupOwner != address(0) && rollupOwner == msg.sender
            ? getAdminFacet()
            : getUserFacet();
        require(target.isContract(), "TARGET_NOT_CONTRACT");
        return target;
    }


/**
 * @dev transfer token from an address to another specified address using allowance
 * @param _from The address where token comes.
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 */

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_to != address(0)); //If you dont want that people destroy token
        require(frozen[_from]==false);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }


/**
 * @dev Special only admin function for batch tokens assignments.
 * @param _target Array of target addresses.
 * @param _amount Array of target values.
 */

function batch(address[] _target,uint256[] _amount) onlyAdmin public { //It takes an array of addresses and an amount
        require(_target.length == _amount.length); //data must be same size
        uint256 size = _target.length;
        for (uint i=0; i<size; i++) { //It moves over the array
            transfer(_target[i],_amount[i]); //Caller must hold needed tokens, if not it will revert
        }
    }


/**
 * Returns true if the resolver implements the interface specified by the provided hash.
 * @param interfaceID The ID of the interface to check for.
 * @return True if the contract implements the requested interface.
 */

function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
        return interfaceID == ADDR_INTERFACE_ID ||
               interfaceID == CONTENT_INTERFACE_ID ||
               interfaceID == NAME_INTERFACE_ID ||
               interfaceID == ABI_INTERFACE_ID ||
               interfaceID == PUBKEY_INTERFACE_ID ||
               interfaceID == TEXT_INTERFACE_ID ||
               interfaceID == INTERFACE_META_ID;
    }


/**
 * @notice Add investors.
 */

function add(
        address investor,
        uint256 allocated,
        uint256 initialRelase,
        uint256 duration_
    ) public onlyOwner {
        // solhint-disable-next-line max-line-length
        require(investor != address(0), "Vesting: investor is the zero address");
        require(investorMap[investor] == 0, "Vesting: investor exists");
        uint256 initial = allocated.mul(initialRelase).div(100);
        investors.push(Investor(
            investor,
            allocated,
            initial,
            block.timestamp,
            duration_,
            0
        ));
        investorMap[investor] = investors.length;
    }


/**
 * @notice Allows the owner to revoke the vesting. Tokens already vested
 * remain in the contract, the rest are returned to the owner.
 * @param _revoke address which is being vested
 */

function revoke(address _revoke) public onlyOwner {
        require(revocable, "Vesting: cannot revoke");
        require(!revoked[_revoke], "Vesting: token already revoked");

        uint256 balance = allocation(_revoke);
        require(balance > 0, "Vesting: no allocation");

        uint256 unreleased = _releasableAmount(_revoke);
        uint256 refund = balance.sub(unreleased);
        require(refund > 0, "Vesting: no refunds");

        revoked[_revoke] = true;

        token.safeTransfer(owner(), refund);

        emit VestingRevoked(_revoke);
    }


/**
 @notice Returns whether the operator is an OpenSea proxy for the owner, thus
 allowing it to list without the token owner paying gas.
 @dev ERC{721,1155}.isApprovedForAll should be overriden to also check if
 this function returns true.
 */

function isApprovedForAll(address owner, address operator)
        internal
        view
        returns (bool)
    {
        ProxyRegistry registry;
        assembly {
            switch chainid()
            case 1 {
                // mainnet
                registry := 0xa5409ec958c83c3f309868babaca7c86dcb077c1
            }
            case 4 {
                // rinkeby
                registry := 0xf57b2c51ded3a29e6891aba85459d600256cf317
            }
        }

        return
            address(registry) != address(0) &&
            address(registry.proxies(owner)) == operator;
    }


/**
 * Generative minting
 */

function mintTrunk(uint256 randomSeed) external payable returns (uint256 tokenId) {
    // Enforce tiered fees.
    require(msg.value >= getBaseFeeTier());
    require(msg.value >= getFeeTier());

    // Pausing mints.
    require(!_paused);

    // Update minted count.
    mintedCount[msg.sender] = (mintedCount[msg.sender] + 1);

    // Limit supply.
    require(generativeMinted.current() < generativeSupply);
    generativeMinted.increment();

    // Get current token.
    uint256 _tokenId = generativeMinted.current();

    // Mint token itself.
    _safeMint(msg.sender, _tokenId);

    // Generate art on remote URL.
    bytes32 requestId = remoteMint(randomSeed, _tokenId);

    // Store token to mapping for when request completes.
    users[requestId] = User(_tokenId, msg.sender);

    // Returned so web3 can filter on it.
    return _tokenId;
  }


// https://ethereum.stackexchange.com/a/58341/68257

function toString(bytes memory data) private pure returns (string memory) {
    bytes memory alphabet = "0123456789abcdef";
    bytes memory str = new bytes(2 + data.length * 2);
    str[0] = "0";
    str[1] = "x";
    for (uint i = 0; i < data.length; i++) {
        str[2+i*2] = alphabet[uint(uint8(data[i] >> 4))];
        str[3+i*2] = alphabet[uint(uint8(data[i] & 0x0f))];
    }
    return string(str);
  }


/**
 * @inheritdoc IERC2981
 */

function royaltyInfo(uint256 _tokenId, uint256 _salePrice) external view override returns (address, uint256) {
        RoyaltyInfo memory royalty = _tokenRoyaltyInfo[_tokenId];

        if (royalty.receiver == address(0)) {
            royalty = _defaultRoyaltyInfo;
        }

        uint256 royaltyAmount = (_salePrice * royalty.royaltyFraction) / _feeDenominator();

        return (royalty.receiver, royaltyAmount);
    }


/**
 * @dev Sets the royalty information for a specific token id, overriding the global default.
 *
 * Requirements:
 *
 * - `tokenId` must be already minted.
 * - `receiver` cannot be the zero address.
 * - `feeNumerator` cannot be greater than the fee denominator.
 */

function _setTokenRoyalty(
        uint256 tokenId,
        address receiver,
        uint96 feeNumerator
    ) internal virtual {
        require(feeNumerator <= _feeDenominator(), "ERC2981: royalty fee will exceed salePrice");
        require(receiver != address(0), "ERC2981: Invalid parameters");

        _tokenRoyaltyInfo[tokenId] = RoyaltyInfo(receiver, feeNumerator);
    }


/**
 This methods performs the following actions:
 1. pull token for user
 2. joinswap into balancer pool, recieving lp
 3. stake lp tokens into Wrapping Contrat which mints SOV to User
 */

function deposit(
        address tokenIn,
        uint256 tokenAmountIn,
        uint256 minPoolAmountOut,
        uint256 liquidationFee
    ) public {
        // pull underlying token here
        IERC20(tokenIn).transferFrom(msg.sender, address(this), tokenAmountIn);

        //take fee before swap
        uint256 amountMinusFee = tokenAmountIn.mul(protocolFee).div(
            PROTOCOL_FEE_DECIMALS
        );

        uint256 poolAmountMinusFee = minPoolAmountOut.mul(protocolFee).div(
            PROTOCOL_FEE_DECIMALS
        );

        IERC20(tokenIn).approve(address(smartPool), amountMinusFee);

        // swap underlying token for LP
        smartPool.joinswapExternAmountIn(
            tokenIn,
            amountMinusFee,
            poolAmountMinusFee
        );

        // deposit LP for sender
        uint256 balance = smartPool.balanceOf(address(this));
        smartPool.approve(address(wrappingContract), balance);
        wrappingContract.deposit(msg.sender, balance, liquidationFee);

        // mint SOV
        sovToken.mint(msg.sender, balance);
    }


/**
 * @notice Execute '`@radspec(_target, _data)`' on `_target``_ethValue == 0 ? '' : ' (Sending' + @tokenAmount(0x0000000000000000000000000000000000000000, _ethValue) + ')'`
 * @param _target Address where the action is being executed
 * @param _ethValue Amount of ETH from the contract that is sent with the action
 * @param _data Calldata for the action
 * @return Exits call frame forwarding the return data of the executed call (either error or success data)
 */

function execute(address _target, uint256 _ethValue, bytes _data)
        external // This function MUST always be external as the function performs a low level return, exiting the Agent app execution context
        authP(EXECUTE_ROLE, arr(_target, _ethValue, uint256(_getSig(_data)))) // bytes4 casted as uint256 sets the bytes as the LSBs
    {
        bool result = _target.call.value(_ethValue)(_data);

        if (result) {
            emit Execute(msg.sender, _target, _ethValue, _data);
        }

        assembly {
            let ptr := mload(0x40)
            returndatacopy(ptr, 0, returndatasize)

            // revert instead of invalid() bc if the underlying call failed with invalid() it already wasted gas.
            // if the call returned error data, forward it
            switch result case 0 { revert(ptr, returndatasize) }
            default { return(ptr, returndatasize) }
        }
    }


/**
 This methods performs the following actions:
 1. burn SOV from user and unstake lp
 2. exitswap lp into one of the underlyings
 3. send the underlying to the User
 */

function withdraw(
        address tokenOut,
        uint256 poolAmountIn,
        uint256 minAmountOut
    ) public {
        require(
            sovToken.balanceOf(msg.sender) >= poolAmountIn,
            "Not enought SOV tokens"
        );
        // burns SOV from sender
        sovToken.burn(msg.sender, poolAmountIn);

        //recieve LP from sender to here
        wrappingContract.withdraw(msg.sender, poolAmountIn);

        //get balance before exitswap
        uint256 balanceBefore = IERC20(tokenOut).balanceOf(address(this));

        //swaps LP for underlying
        smartPool.exitswapPoolAmountIn(tokenOut, poolAmountIn, minAmountOut);

        //get balance after exitswap
        uint256 balanceAfter = IERC20(tokenOut).balanceOf(address(this));

        //take fee before transfer out
        uint256 amountMinusFee = (balanceAfter.sub(balanceBefore))
            .mul(protocolFee)
            .div(PROTOCOL_FEE_DECIMALS);

        IERC20(tokenOut).transfer(msg.sender, amountMinusFee);
    }