/**
 * @dev deploy a new set of contracts for the Panel, with all params needed by contracts. Set the minter address for Token contract,
 * Owner is set as a manager in WL, Funding and FundsUnlocker, DEX is whitelisted
 * @param _name name of the token to be deployed
 * @param _symbol symbol of the token to be deployed
 * @param _setDocURL URL of the document describing the Panel
 * @param _setDocHash hash of the document describing the Panel
 * @param _exchRateSeed exchange rate between SEED tokens received and tokens given to the SEED sender (multiply by 10^_exchRateDecim)
 * @param _exchRateOnTop exchange rate between SEED token received and tokens minted on top (multiply by 10^_exchRateDecim)
 * @param _seedMaxSupply max supply of SEED tokens accepted by this contract
 * @param _WLAnonymThr max anonym threshold
 */

function deployPanelContracts(string memory _name, string memory _symbol, string memory _setDocURL, bytes32 _setDocHash,
                            uint256 _exchRateSeed, uint256 _exchRateOnTop, uint256 _seedMaxSupply, uint256 _WLAnonymThr) public {
        address sender = msg.sender;

        require(sender != address(0), "Sender Address is zero");
        require(internalDEXAddress != address(0), "Internal DEX Address is zero");

        deployers[sender] = true;
        deployerList.push(sender);
        deployerLength = deployerList.length;

        address newAT = deployerAT.newAdminTools(_WLAnonymThr);
        ATContracts[newAT] = true;
        ATContractsList.push(newAT);
        address newT = deployerT.newToken(sender, _name, _symbol, newAT);
        TContracts[newT] = true;
        TContractsList.push(newT);
        address newFP = deployerFP.newFundingPanel(sender, _setDocURL, _setDocHash, _exchRateSeed, _exchRateOnTop,
                                            seedAddress, _seedMaxSupply, newT, newAT, (deployerLength-1));
        FPContracts[newFP] = true;
        FPContractsList.push(newFP);

        IAdminTools ATBrandNew = IAdminTools(newAT);
        ATBrandNew.setFFPAddresses(address(this), newFP);
        ATBrandNew.setMinterAddress(newFP);
        ATBrandNew.addWLManagers(address(this));
        ATBrandNew.addWLManagers(sender);
        ATBrandNew.addFundingManagers(sender);
        ATBrandNew.addFundsUnlockerManagers(sender);
        ATBrandNew.setWalletOnTopAddress(sender);

        uint256 dexMaxAmnt = _exchRateSeed.mul(300000000);  //Seed total supply
        ATBrandNew.addToWhitelist(internalDEXAddress, dexMaxAmnt);

        uint256 onTopMaxAmnt = _seedMaxSupply.mul(_exchRateSeed).div(10**18);
        ATBrandNew.addToWhitelist(sender, onTopMaxAmnt);

        ATBrandNew.removeWLManagers(address(this));

        Ownable customOwnable = Ownable(newAT);
        customOwnable.transferOwnership(sender);

        emit NewPanelCreated(sender, newAT, newT, newFP, deployerLength);
    }


//  tokenURI(): returns a URL to an ERC-721 standard JSON file
//

function tokenURI(uint256 _tokenId)
      public
      view
      validPointId(_tokenId)
      returns (string _tokenURI)
    {
      _tokenURI = "https://azimuth.network/erc721/0000000000.json";
      bytes memory _tokenURIBytes = bytes(_tokenURI);
      _tokenURIBytes[31] = byte(48+(_tokenId / 1000000000) % 10);
      _tokenURIBytes[32] = byte(48+(_tokenId / 100000000) % 10);
      _tokenURIBytes[33] = byte(48+(_tokenId / 10000000) % 10);
      _tokenURIBytes[34] = byte(48+(_tokenId / 1000000) % 10);
      _tokenURIBytes[35] = byte(48+(_tokenId / 100000) % 10);
      _tokenURIBytes[36] = byte(48+(_tokenId / 10000) % 10);
      _tokenURIBytes[37] = byte(48+(_tokenId / 1000) % 10);
      _tokenURIBytes[38] = byte(48+(_tokenId / 100) % 10);
      _tokenURIBytes[39] = byte(48+(_tokenId / 10) % 10);
      _tokenURIBytes[40] = byte(48+(_tokenId / 1) % 10);
    }


//
//  Point rules
//
//  getSpawnLimit(): returns the total number of children the _point
//                   is allowed to spawn at _time.
//

function getSpawnLimit(uint32 _point, uint256 _time)
      public
      view
      returns (uint32 limit)
    {
      Azimuth.Size size = azimuth.getPointSize(_point);

      if ( size == Azimuth.Size.Galaxy )
      {
        return 255;
      }
      else if ( size == Azimuth.Size.Star )
      {
        //  in 2019, stars may spawn at most 1024 planets. this limit doubles
        //  for every subsequent year.
        //
        //    Note: 1546300800 corresponds to 2019-01-01
        //
        uint256 yearsSince2019 = (_time - 1546300800) / 365 days;
        if (yearsSince2019 < 6)
        {
          limit = uint32( 1024 * (2 ** yearsSince2019) );
        }
        else
        {
          limit = 65535;
        }
        return limit;
      }
      else  //  size == Azimuth.Size.Planet
      {
        //  planets can create moons, but moons aren't on the chain
        //
        return 0;
      }
    }


// Retrieve amount from unused lockBoxes

function withdrawUnusedYield(uint256 lockBoxNumber) public onlyOwner {
        // Check for deposit time end
        require(now > endDepositTime, "Deposit still possible.");

        LockBoxStruct storage l = lockBoxStructs[lockBoxNumber];

        // Check for non-deposition of tokens
        require(l.tokensDeposited == false, "Tokens were deposited, active lockbox!");
        uint256 amount = l.balance;
        l.balance = 0;
        require(token.transfer(yieldWallet, amount), "Tokens cannot be transferred.");
    }


//  setTransferProxy(): give _transferProxy the right to transfer _point
//
//    Requirements:
//    - :msg.sender must be either _point's current owner,
//      or be an operator for the current owner
//

function setTransferProxy(uint32 _point, address _transferProxy)
      public
    {
      //  owner: owner of _point
      //
      address owner = azimuth.getOwner(_point);

      //  caller must be :owner, or an operator designated by the owner.
      //
      require((owner == msg.sender) || azimuth.isOperator(owner, msg.sender));

      //  set transfer proxy field in Azimuth contract
      //
      azimuth.setTransferProxy(_point, _transferProxy);

      //  emit Approval event
      //
      emit Approval(owner, _transferProxy, uint256(_point));
    }


/**
 * @dev Redeem user mintable tokens. Only the mining contract can redeem tokens.
 * @param to The user that will receive the redeemed token.
 * @param amount The amount of tokens to be withdrawn
 * @return The result of the redeem
 */

function redeem(address to, uint256 amount) external returns (bool success) {
        require(msg.sender == mintableAddress);
        require(_isIssuable == true);
        require(amount > 0);

        // The total amount of redeem tokens to the user.
        _amountRedeem[to] += amount;

        // Mint new tokens and send the funds to the account `mintableAddress`
        // Users can withdraw funds.
        mintToken(mintableAddress, amount);

        return true;
    }


/**
 * @dev The user can withdraw his minted tokens.
 * @param amount The amount of tokens to be withdrawn
 * @return The result of the withdraw
 */

function withdraw(uint256 amount) public returns (bool success) {
        require(_isIssuable == true);

        // Safety check
        require(amount > 0);
        require(amount <= _amountRedeem[msg.sender]);
        require(amount >= MIN_WITHDRAW_AMOUNT);

        // Transfer the amount of tokens in the mining contract `mintableAddress` to the user account
        require(balances[mintableAddress] >= amount);

        // The balance of the user redeemed tokens.
        _amountRedeem[msg.sender] -= amount;

        // Keep track of the tokens minted by the user.
        _amountMinted[msg.sender] += amount;

        balances[mintableAddress] -= amount;
        balances[msg.sender] += amount;

        emit Transfer(mintableAddress, msg.sender, amount);
        return true;
    }


// ------------------------------------------------------------------------
// Transfer the balance from token owner's account to `to` account
// - Owner's account must have sufficient balance to transfer
// - Zero value transfers are allowed
// - takes in locking Period to lock the tokens to be used
// - if want to transfer without locking enter zero in lockingPeriod argument
// ------------------------------------------------------------------------

function distributeTokens(address to, uint tokens, uint256 lockingPeriod) onlyOwner public returns (bool success) {
            // transfer tokens to the "to" address
            transfer(to, tokens);
            // if there is no lockingPeriod, add coins to unLockedCoins per address
            if(lockingPeriod == 0)
                unLockedCoins[to] = unLockedCoins[to].add(tokens);
            // if there is a lockingPeriod, add coins to record mapping
            else
                _addRecord(to, tokens, lockingPeriod);
            return true;
        }


// ------------------------------------------------------------------------
// Checks if there is any uunLockedCoins available
// ------------------------------------------------------------------------

function _updateUnLockedCoins(address _from, uint tokens) private returns (bool success) {
            // if unLockedCoins are greater than "tokens" of "to", initiate transfer
            if(unLockedCoins[_from] >= tokens){
                return true;
            }
            // if unLockedCoins are less than "tokens" of "to", update unLockedCoins by checking record with "now" time
            else{
                _updateRecord(_from);
                // check if unLockedCoins are greater than "token" of "to", initiate transfer
                if(unLockedCoins[_from] >= tokens){
                    return true;
                }
                // otherwise revert
                else{
                    revert();
                }
            }
        }


// ------------------------------------------------------------------------
// Unlock the coins if lockingPeriod is expired
// ------------------------------------------------------------------------

function _updateRecord(address _address) private returns (bool success){
            PC[] memory tempArray = record[_address];
            uint tempCount = 0;
            for(uint i=0; i < tempArray.length; i++){
                if(tempArray[i].lockingPeriod < now && tempArray[i].added == false){
                    tempCount = tempCount.add(tempArray[i].coins);
                    tempArray[i].added = true;
                    record[_address][i] = PC(tempArray[i].lockingPeriod, tempArray[i].coins, tempArray[i].added);
                }
            }
            unLockedCoins[_address] = unLockedCoins[_address].add(tempCount);
            return true;
        }


// Update the given pool's TOKEN allocation point and deposit fee. Can only be called by the owner.

function set(uint256 _pid, uint256 _allocPoint, uint16 _depositFeeBP, uint256 totalMaxRewardToken, bool _withUpdate)
        public
        onlyOwner
    {
        require(_depositFeeBP <= 10000, "set: invalid deposit fee basis points");

        if (_withUpdate) {
            massUpdatePools();
        }

        require(poolInfo[_pid].totalRewardTokens <= totalMaxRewardToken, "set: totalMaxRewardToken is invalid");

        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);

        poolInfo[_pid].allocPoint = _allocPoint;
        poolInfo[_pid].depositFeeBP = _depositFeeBP;
        poolInfo[_pid].totalMaxRewardTokens = totalMaxRewardToken;
    }


//
// Check crowdsale state and calibrate it
//

function checkCrowdsaleState() internal returns (bool) {
        if (ethRaised >= maxCap && crowdsaleState != state.crowdsaleEnded) {// Check if max cap is reached
            crowdsaleState = state.crowdsaleEnded;
            CrowdsaleEnded(block.number);
            // Raise event
            return true;
        }

        if (now >= END_TIME) {
            crowdsaleState = state.crowdsaleEnded;
            CrowdsaleEnded(block.number);
            // Raise event
            return true;
        }

        if (now >= BEGIN_TIME && now < END_TIME) {// Check if we are in crowdsale state
            if (crowdsaleState != state.crowdsale) {// Check if state needs to be changed
                crowdsaleState = state.crowdsale;
                // Set new state
                CrowdsaleStarted(block.number);
                // Raise event
                return true;
            }
        }

        return false;
    }


//
// Owner can batch return contributors contributions(eth)
//

function batchReturnEthIfFailed(uint _numberOfReturns) onlyOwner public {
        require(crowdsaleState != state.crowdsaleEnded);
        // Check if crowdsale has ended
        require(ethRaised < minCap);
        // Check if crowdsale has failed
        address currentParticipantAddress;
        uint contribution;
        for (uint cnt = 0; cnt < _numberOfReturns; cnt++) {
            currentParticipantAddress = contributorIndexes[nextContributorToClaim];
            // Get next unclaimed participant
            if (currentParticipantAddress == 0x0) return;
            // Check if all the participants were compensated
            if (!hasClaimedEthWhenFail[currentParticipantAddress]) {// Check if participant has already claimed
                contribution = contributorList[currentParticipantAddress].contributionAmount;
                // Get contribution of participant
                hasClaimedEthWhenFail[currentParticipantAddress] = true;
                // Set that he has claimed
                balances[currentParticipantAddress] = 0;
                if (!currentParticipantAddress.send(contribution)) {// Refund eth
                    ErrorSendingETH(currentParticipantAddress, contribution);
                    // If there is an issue raise event for manual recovery
                }
            }
            nextContributorToClaim += 1;
            // Repeat
        }
    }


// Parking off

function parkingOff (address node) public {
        if (!parkingSwitches[msg.sender].initialized) revert();

        // Calculate the amount depending on rate
        uint256 amount = (now - parkingSwitches[msg.sender].startTime) * parkingSwitches[msg.sender].fixedRate;
        // Maximum can be predefinedAmount, if it less than that, return tokens
        amount = amount > parkingSwitches[msg.sender].predefinedAmount ? parkingSwitches[msg.sender].predefinedAmount : amount;

        balances[node] = balances[node] + amount;
        reservedFundsParking[msg.sender] = reservedFundsParking[msg.sender] - amount;

        //
        if (reservedFundsParking[msg.sender] > 0) {
            balances[msg.sender] = balances[msg.sender] + reservedFundsParking[msg.sender];
            // all tokens taken, set to 0
            reservedFundsParking[msg.sender] = 0;
        }

        // Uninitialize
        parkingSwitches[msg.sender].node = 0;
        parkingSwitches[msg.sender].startTime = 0;
        parkingSwitches[msg.sender].endTime = 0;
        parkingSwitches[msg.sender].fixedRate = 0;
        parkingSwitches[msg.sender].initialized = false;
        parkingSwitches[msg.sender].predefinedAmount = 0;
    }


/**
 * @notice Create a bond by depositing `amount` of `principal`.
 * Principal will be transferred from `msg.sender` using `allowance`.
 * @param amount Amount of principal to deposit.
 * @param minAmountOut The minimum **SOLACE** or **xSOLACE** out.
 * @param depositor The bond recipient, default msg.sender.
 * @param stake True to stake, false to not stake.
 * @return payout The amount of SOLACE or xSOLACE in the bond.
 * @return bondID The ID of the newly created bond.
 */

function deposit(
        uint256 amount,
        uint256 minAmountOut,
        address depositor,
        bool stake
    ) external override returns (uint256 payout, uint256 bondID) {
        // pull tokens
        SafeERC20.safeTransferFrom(principal, msg.sender, address(this), amount);
        // accounting
        return _deposit(amount, minAmountOut, depositor, stake);
    }


/**
 * @notice Create a bond by depositing `amount` of `principal`.
 * Principal will be transferred from `depositor` using `permit`.
 * Note that not all ERC20s have a permit function, in which case this function will revert.
 * @param amount Amount of principal to deposit.
 * @param minAmountOut The minimum **SOLACE** or **xSOLACE** out.
 * @param depositor The bond recipient, default msg.sender.
 * @param stake True to stake, false to not stake.
 * @param deadline Time the transaction must go through before.
 * @param v secp256k1 signature
 * @param r secp256k1 signature
 * @param s secp256k1 signature
 * @return payout The amount of SOLACE or xSOLACE in the bond.
 * @return bondID The ID of the newly created bond.
 */

function depositSigned(
        uint256 amount,
        uint256 minAmountOut,
        address depositor,
        bool stake,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external override returns (uint256 payout, uint256 bondID) {
        // permit
        IERC20Permit(address(principal)).permit(depositor, address(this), amount, deadline, v, r, s);
        // pull tokens
        SafeERC20.safeTransferFrom(principal, depositor, address(this), amount);
        // accounting
        return _deposit(amount, minAmountOut, depositor, stake);
    }


/**
 * @notice Approve of a specific `tokenID` for spending by `spender` via signature.
 * @param spender The account that is being approved.
 * @param tokenID The ID of the token that is being approved for spending.
 * @param deadline The deadline timestamp by which the call must be mined for the approve to work.
 * @param v Must produce valid secp256k1 signature from the holder along with `r` and `s`.
 * @param r Must produce valid secp256k1 signature from the holder along with `v` and `s`.
 * @param s Must produce valid secp256k1 signature from the holder along with `r` and `v`.
 */

function permit(
        address spender,
        uint256 tokenID,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external override {
        require(_exists(tokenID), "query for nonexistent token");
        require(block.timestamp <= deadline, "permit expired");

        uint256 nonce = _nonces[tokenID]++; // get then increment
        bytes32 digest =
            keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(abi.encode(_PERMIT_TYPEHASH, spender, tokenID, nonce, deadline))
                )
            );
        address owner = ownerOf(tokenID);
        require(spender != owner, "cannot permit to self");

        if (Address.isContract(owner)) {
            require(IERC1271(owner).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e, "unauthorized");
        } else {
            address recoveredAddress = ecrecover(digest, v, r, s);
            require(recoveredAddress != address(0), "invalid signature");
            require(recoveredAddress == owner, "unauthorized");
        }

        _approve(spender, tokenID);
    }


/**
 * @notice Lists all tokens.
 * Order not specified.
 * @dev This function is more useful off chain than on chain.
 * @return tokenIDs The list of token IDs.
 */

function listTokens() public view override returns (uint256[] memory tokenIDs) {
        uint256 tokenCount = totalSupply();
        tokenIDs = new uint256[](tokenCount);
        for(uint256 index = 0; index < tokenCount; index++) {
            tokenIDs[index] = tokenByIndex(index);
        }
        return tokenIDs;
    }


/// @notice Makes permit calls indicated by a struct
/// @param data the struct which has the permit calldata

function _permitCall(PermitData[] memory data) internal {
        // Make the permit call to the token in the data field using
        // the fields provided.
        if (data.length != 0) {
            // We make permit calls for each indicated call
            for (uint256 i = 0; i < data.length; i++) {
                data[i].tokenContract.permit(
                    msg.sender,
                    data[i].spender,
                    data[i].amount,
                    data[i].expiration,
                    data[i].v,
                    data[i].r,
                    data[i].s
                );
            }
        }
    }


/// @notice This function sets approvals on all ERC20 tokens.
/// @param tokens An array of token addresses which are to be approved
/// @param spenders An array of contract addresses, most likely curve and
/// balancer pool addresses
/// @param amounts An array of amounts for which at each index, the spender
/// from the same index in the spenders array is approved to use the token
/// at the equivalent index of the token array on behalf of this contract

function setApprovalsFor(
        address[] memory tokens,
        address[] memory spenders,
        uint256[] memory amounts
    ) external onlyAuthorized {
        require(tokens.length == spenders.length, "Incorrect length");
        require(tokens.length == amounts.length, "Incorrect length");
        for (uint256 i = 0; i < tokens.length; i++) {
            // Below call is to make sure that previous allowance shouldn't revert the transaction
            // It is just a safety pattern to use.
            IERC20(tokens[i]).safeApprove(spenders[i], uint256(0));
            IERC20(tokens[i]).safeApprove(spenders[i], amounts[i]);
        }
    }


/// @notice Swaps an amount of curve LP tokens for a single root token
/// @param _zap See ZapCurveLpOut
/// @param _lpTokenAmount This is the amount of lpTokens we are swapping
/// with
/// @param _minRootTokenAmount This is the minimum amount of "root" tokens
/// the user expects to swap for. Used only in the final zap when executed
/// under zapOut
/// @param _recipient The address which the outputs tokens are to be sent
/// to. When there is a second zap to occur, in the first zap the recipient
/// should be this address

function _zapCurveLpOut(
        ZapCurveLpOut memory _zap,
        uint256 _lpTokenAmount,
        uint256 _minRootTokenAmount,
        address payable _recipient
    ) internal returns (uint256 rootAmount) {
        // Flag to detect if we are sending to recipient
        bool transferToRecipient = address(this) != _recipient;
        uint256 beforeAmount = _zap.rootToken == _ETH_CONSTANT
            ? address(this).balance
            : _getBalanceOf(IERC20(_zap.rootToken));

        if (_zap.curveRemoveLiqFnIsUint256) {
            ICurvePool(_zap.curvePool).remove_liquidity_one_coin(
                _lpTokenAmount,
                uint256(int256(_zap.rootTokenIdx)),
                _minRootTokenAmount
            );
        } else {
            ICurvePool(_zap.curvePool).remove_liquidity_one_coin(
                _lpTokenAmount,
                _zap.rootTokenIdx,
                _minRootTokenAmount
            );
        }

        // ETH case
        if (_zap.rootToken == _ETH_CONSTANT) {
            // Get ETH balance of current contract
            rootAmount = address(this).balance - beforeAmount;
            // if address does not equal this contract we send funds to recipient
            if (transferToRecipient) {
                // Send rootAmount of ETH to the user-specified recipient
                _recipient.transfer(rootAmount);
            }
        } else {
            // Get balance of root token that was swapped
            rootAmount = _getBalanceOf(IERC20(_zap.rootToken)) - beforeAmount;
            // Send tokens to recipient
            if (transferToRecipient) {
                IERC20(_zap.rootToken).safeTransferFrom(
                    address(this),
                    _recipient,
                    rootAmount
                );
            }
        }
    }


/**
 * @notice token contructor.
 * @param _teamAddress is the address of the developer team
 */

function AssetUNR(address _teamAddress) public {
        require(msg.sender != _teamAddress);
        totalSupply = 100000000 * (10 ** decimals); //100 million tokens initial supply;
        balances[msg.sender] = 88000000 * (10 ** decimals); //88 million supply is initially holded by contract creator for the ICO, marketing and bounty
        balances[_teamAddress] = 11900000 * (10 ** decimals); //11.9 million supply is initially holded by developer team
        balances[0xFAB6368b0F7be60c573a6562d82469B5ED9e7eE6] = 100000 * (10 ** decimals); //0.1 million supply is initially holded by contract writer

        Transfer(0, this, totalSupply);
        Transfer(this, msg.sender, balances[msg.sender]);
        Transfer(this, _teamAddress, balances[_teamAddress]);
        Transfer(this, 0xFAB6368b0F7be60c573a6562d82469B5ED9e7eE6, balances[0xFAB6368b0F7be60c573a6562d82469B5ED9e7eE6]);
    }


/**
 *   @dev Contract constructor function
 */

function Play2liveICO(
        address _preSaleToken,
        address _Company,
        address _OperationsFund,
        address _FoundersFund,
        address _PartnersFund,
        address _AdvisorsFund,
        address _BountyFund,
        address _Manager,
        address _Controller_Address1,
        address _Controller_Address2,
        address _Controller_Address3,
        address _Oracle
        ) public {
        preSaleToken = Presale(_preSaleToken);
        Company = _Company;
        OperationsFund = _OperationsFund;
        FoundersFund = _FoundersFund;
        PartnersFund = _PartnersFund;
        AdvisorsFund = _AdvisorsFund;
        BountyFund = _BountyFund;
        Manager = _Manager;
        Controller_Address1 = _Controller_Address1;
        Controller_Address2 = _Controller_Address2;
        Controller_Address3 = _Controller_Address3;
        Oracle = _Oracle;
    }


/**
 *   @dev Function to finish ICO
 *   Sets ICO status to IcoFinished and  emits tokens for funds
 */

function finishIco() external managerOnly {
        require(statusICO == StatusICO.IcoStarted || statusICO == StatusICO.IcoPaused);
        uint alreadyMinted = LUC.totalSupply();
        uint totalAmount = alreadyMinted.mul(1000).div(publicIcoPart);
        LUC.mintTokens(OperationsFund, operationsPart.mul(totalAmount).div(1000));
        LUC.mintTokens(FoundersFund, foundersPart.mul(totalAmount).div(1000));
        LUC.mintTokens(PartnersFund, partnersPart.mul(totalAmount).div(1000));
        LUC.mintTokens(AdvisorsFund, advisorsPart.mul(totalAmount).div(1000));
        LUC.mintTokens(BountyFund, bountyPart.mul(totalAmount).div(1000));
        statusICO = StatusICO.IcoFinished;
        LogFinishICO();
    }


/**
 *   @dev Function to issue tokens for investors who paid in ether
 *   @param _investor     address which the tokens will be issued tokens
 *   @param _lucValue     number of LUC tokens
 */

function buy(address _investor, uint _lucValue) internal {
        require(statusICO == StatusICO.PreIcoStarted || statusICO == StatusICO.IcoStarted);
        uint bonus = getBonus(_lucValue);
        uint total = _lucValue.add(bonus);
        require(soldAmount + _lucValue <= hardCap);
        LUC.mintTokens(_investor, total);
        soldAmount = soldAmount.add(_lucValue);
    }


// user can also burn by sending token to address(0), but this function will emit Burned event

function burn(uint256 _amount) public returns (bool) {
        require (_amount != 0, "burn amount should not be zero");
        require(balances[msg.sender] >= _amount);
        totalSupply_ = totalSupply_.sub(_amount);
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        emit Burned(msg.sender, _amount);
        emit Transfer(msg.sender, address(0), _amount);
        _moveDelegates(delegates[msg.sender], address(0), _amount);
        return true;
    }


/**
 * @dev Attribution to the awesome delta.financial contracts
 */

function marketParticipationAgreement()
        public
        pure
        returns (string memory)
    {
        return
            "I understand that I am interacting with a smart contract. I understand that tokens commited are subject to the token issuer and local laws where applicable. I have reviewed the code of this smart contract and understand it fully. I agree to not hold developers or other people associated with the project liable for any losses or misunderstandings";
    }


/**
 * @notice Commit ETH to buy tokens on auction.
 * @param _beneficiary Auction participant ETH address.
 */

function commitEth(
        address payable _beneficiary,
        bool readAndAgreedToMarketParticipationAgreement
    ) public payable {
        require(
            paymentCurrency == ETH_ADDRESS,
            "BatchAuction: payment currency is not ETH"
        );

        require(msg.value > 0, "BatchAuction: Value must be higher than 0");
        if (readAndAgreedToMarketParticipationAgreement == false) {
            revertBecauseUserDidNotProvideAgreement();
        }
        _addCommitment(_beneficiary, msg.value);
    }


/**
 * @notice Checks if amout not 0 and makes the transfer and adds commitment.
 * @dev Users must approve contract prior to committing tokens to auction.
 * @param _from User ERC20 address.
 * @param _amount Amount of approved ERC20 tokens.
 */

function commitTokensFrom(
        address _from,
        uint256 _amount,
        bool readAndAgreedToMarketParticipationAgreement
    ) public nonReentrant {
        require(
            paymentCurrency != ETH_ADDRESS,
            "BatchAuction: Payment currency is not a token"
        );
        if (readAndAgreedToMarketParticipationAgreement == false) {
            revertBecauseUserDidNotProvideAgreement();
        }
        require(_amount > 0, "BatchAuction: Value must be higher than 0");
        _safeTransferFrom(paymentCurrency, msg.sender, _amount);
        _addCommitment(_from, _amount);
    }


/**
 * @notice Updates commitment for this address and total commitment of the auction.
 * @param _addr Auction participant address.
 * @param _commitment The amount to commit.
 */

function _addCommitment(address _addr, uint256 _commitment) internal {
        require(
            block.timestamp >= marketInfo.startTime &&
                block.timestamp <= marketInfo.endTime,
            "BatchAuction: outside auction hours"
        );

        uint256 newCommitment = commitments[_addr].add(_commitment);
        commitments[_addr] = newCommitment;
        marketStatus.commitmentsTotal = BoringMath.to128(
            uint256(marketStatus.commitmentsTotal).add(_commitment)
        );
        emit AddedCommitment(_addr, _commitment);
    }


///--------------------------------------------------------
/// Finalize Auction
///--------------------------------------------------------
/// @notice Auction finishes successfully above the reserve
/// @dev Transfer contract funds to initialized wallet.

function finalize() public nonReentrant {
        require(
            hasAdminRole(msg.sender) ||
                wallet == msg.sender ||
                hasSmartContractRole(msg.sender) ||
                finalizeTimeExpired(),
            "BatchAuction: Sender must be admin"
        );
        require(
            !marketStatus.finalized,
            "BatchAuction: Auction has already finalized"
        );
        require(
            block.timestamp > marketInfo.endTime,
            "BatchAuction: Auction has not finished yet"
        );
        if (auctionSuccessful()) {
            /// @dev Successful auction
            /// @dev Transfer contributed tokens to wallet.
            _safeTokenPayment(
                paymentCurrency,
                wallet,
                uint256(marketStatus.commitmentsTotal)
            );
        } else {
            /// @dev Failed auction
            /// @dev Return auction tokens back to wallet.
            _safeTokenPayment(auctionToken, wallet, marketInfo.totalTokens);
        }
        marketStatus.finalized = true;
        startReleaseTime = block.timestamp;
        emit AuctionFinalized();
    }


/**
 * @notice Cancel Auction
 * @dev Admin can cancel the auction before it starts
 */

function cancelAuction() public nonReentrant {
        require(hasAdminRole(msg.sender));
        MarketStatus storage status = marketStatus;
        require(!status.finalized, "Crowdsale: already finalized");
        require(
            uint256(status.commitmentsTotal) == 0,
            "Crowdsale: Funds already raised"
        );

        _safeTokenPayment(
            auctionToken,
            wallet,
            uint256(marketInfo.totalTokens)
        );

        status.finalized = true;
        emit AuctionCancelled();
    }


/// @notice Withdraw your tokens once the Auction has ended.

function withdrawTokens(address payable beneficiary) public nonReentrant {
        if (auctionSuccessful()) {
            require(marketStatus.finalized, "BatchAuction: not finalized");
            /// @dev Successful auction! Transfer claimed tokens.
            uint256 tokensToClaim = tokensClaimable(beneficiary);
            require(tokensToClaim > 0, "BatchAuction: No tokens to claim");
            _safeTokenPayment(auctionToken, beneficiary, tokensToClaim);
            claimed[beneficiary] = claimed[beneficiary].add(tokensToClaim);
        } else {
            /// @dev Auction did not meet reserve price.
            /// @dev Return committed funds back to user.
            require(
                block.timestamp > marketInfo.endTime,
                "BatchAuction: Auction has not finished yet"
            );
            uint256 fundsCommitted = commitments[beneficiary];
            require(fundsCommitted > 0, "BatchAuction: No funds committed");
            commitments[beneficiary] = 0; // Stop multiple withdrawals and free some gas
            _safeTokenPayment(paymentCurrency, beneficiary, fundsCommitted);
        }
    }


/**
 * @notice How many tokens the user is able to claim.
 * @param _user Auction participant address.
 * @return claimable Tokens left to claim.
 */

function tokensClaimable(address _user)
        public
        view
        returns (uint256)
    {
        if (!marketStatus.finalized) return 0;
        if (commitments[_user] == 0) return 0;
        uint256 unclaimedTokens = IERC20(auctionToken).balanceOf(address(this));
        uint256 claimerCommitment = _getTokenAmount(commitments[_user]);

        uint256 remainingToken = getRemainingAmount(_user);
        uint256 claimable = claimerCommitment.sub(remainingToken).sub(claimed[_user]);

        if (claimable > unclaimedTokens) {
            claimable = unclaimedTokens;
        }
        return claimable;
    }


/**
 * @notice Admin can set start and end time through this function.
 * @param _startTime Auction start time.
 * @param _endTime Auction end time.
 */

function setAuctionTime(uint256 _startTime, uint256 _endTime) external {
        require(hasAdminRole(msg.sender));
        require(
            _startTime < 10000000000,
            "BatchAuction: enter an unix timestamp in seconds, not miliseconds"
        );
        require(
            _endTime < 10000000000,
            "BatchAuction: enter an unix timestamp in seconds, not miliseconds"
        );
        require(
            _startTime >= block.timestamp,
            "BatchAuction: start time is before current time"
        );
        require(
            _endTime > _startTime,
            "BatchAuction: end time must be older than start price"
        );

        require(
            marketStatus.commitmentsTotal == 0,
            "BatchAuction: auction cannot have already started"
        );

        marketInfo.startTime = BoringMath.to64(_startTime);
        marketInfo.endTime = BoringMath.to64(_endTime);

        emit AuctionTimeUpdated(_startTime, _endTime);
    }


/**
 * @dev Batch transfer both.
 */

function batchTransfer(address payable[] memory accounts, uint256 etherValue, uint256 vokenValue) public payable {
        uint256 __etherBalance = address(this).balance;
        uint256 __vokenAllowance = VOKEN.allowance(msg.sender, address(this));

        require(__etherBalance >= etherValue.mul(accounts.length));
        require(__vokenAllowance >= vokenValue.mul(accounts.length));

        for (uint256 i = 0; i < accounts.length; i++) {
            accounts[i].transfer(etherValue);
            assert(VOKEN.transferFrom(msg.sender, accounts[i], vokenValue));
        }
    }


/**
 * @dev Batch transfer Voken.
 */

function batchTransferVoken(address[] memory accounts, uint256 vokenValue) public {
        uint256 __vokenAllowance = VOKEN.allowance(msg.sender, address(this));

        require(__vokenAllowance >= vokenValue.mul(accounts.length));

        for (uint256 i = 0; i < accounts.length; i++) {
            assert(VOKEN.transferFrom(msg.sender, accounts[i], vokenValue));
        }
    }


/**
 * @dev Add all pre-minted tokens to the company wallet.
 */

function init() external onlyOwner {
        require(inGroup[companyWallet] == 0, "Already init");
        uint256 balance = tokenContract.balanceOf(address(this));
        require(balance > 0, "No pre-minted tokens");
        balances[companyWallet] = balance; //Transfer all pre-minted tokens to company wallet.
        _addPremintedWallet(companyWallet, 0); // add company wallet to the company group.
        totalSupply = safeAdd(totalSupply, balance);
        emit Transfer(address(0), companyWallet, balance);
    }


/**
 * @notice Initializes the teller.
 * @param name_ The name of the bond token.
 * @param governance_ The address of the [governor](/docs/protocol/governance).
 * @param solace_ The SOLACE token.
 * @param xsolace_ The xSOLACE token.
 * @param pool_ The underwriting pool.
 * @param dao_ The DAO.
 * @param principal_ address The ERC20 token that users deposit.
 * @param bondDepo_ The bond depository.
 */

function initialize(
        string memory name_,
        address governance_,
        address solace_,
        address xsolace_,
        address pool_,
        address dao_,
        address principal_,
        address bondDepo_
    ) external override initializer {
        __Governable_init(governance_);
        string memory symbol = "SBT";
        __ERC721Enhanced_init(name_, symbol);
        _setAddresses(solace_, xsolace_, pool_, dao_, principal_, bondDepo_);
    }


/**
 * @notice Calculate the amount of **SOLACE** or **xSOLACE** out for an amount of `principal`.
 * @param amountIn Amount of principal to deposit.
 * @param stake True to stake, false to not stake.
 * @return amountOut Amount of **SOLACE** or **xSOLACE** out.
 */

function calculateAmountOut(uint256 amountIn, bool stake) external view override returns (uint256 amountOut) {
        require(termsSet, "not initialized");
        // exchange rate
        uint256 bondPrice_ = bondPrice();
        require(bondPrice_ > 0, "zero price");
        amountOut = 1 ether * amountIn / bondPrice_; // 1 ether => 1 solace
        // ensure there is remaining capacity for bond
        if (capacityIsPayout) {
            // capacity in payout terms
            require(capacity >= amountOut, "bond at capacity");
        } else {
            // capacity in principal terms
            require(capacity >= amountIn, "bond at capacity");
        }
        require(amountOut <= maxPayout, "bond too large");
        // route solace
        uint256 bondFee = amountOut * bondFeeBps / MAX_BPS;
        if(bondFee > 0) {
            amountOut -= bondFee;
        }
        // optionally stake
        if(stake) {
            amountOut = xsolace.solaceToXSolace(amountOut);
        }
        return amountOut;
    }


/**
 * @dev Create new wallet in Escrow contract
 * @param newWallet The wallet address
 * @param groupId The group ID. Wallet with goal can be added only in group 1
 * @param value The amount of token transfer from company wallet to created wallet
 * @param goal The amount in USD, that investor should receive before splitting liquidity with others members
 * @return true when if wallet created.
 */

function createWallet(address newWallet, uint256 groupId, uint256 value, uint256 goal) external onlyCompany returns(bool){
        require(inGroup[newWallet] == 0, "Wallet already added");
        if (goal != 0) {
            require(groupId == 1, "Wallet with goal disallowed");
            goals[newWallet] = goal;
        }
        _addPremintedWallet(newWallet, groupId);
        return _transfer(newWallet, value);
    }


/**
 * @notice Redeem a bond.
 * Bond must be matured.
 * Redeemer must be owner or approved.
 * @param bondID The ID of the bond to redeem.
 */

function redeem(uint256 bondID) external override nonReentrant tokenMustExist(bondID) {
        // checks
        Bond memory bond = bonds[bondID];
        require(_isApprovedOrOwner(msg.sender, bondID), "!bonder");
        require(block.timestamp >= bond.maturation, "bond not yet redeemable");
        // send payout
        SafeERC20.safeTransfer(IERC20(bond.payoutToken), msg.sender, bond.payoutAmount);
        // delete bond
        _burn(bondID);
        delete bonds[bondID];
        emit RedeemBond(bondID, msg.sender, bond.payoutToken, bond.payoutAmount);
    }


/**
 * @notice Calculate the payout in **SOLACE** and update the current price of a bond.
 * @param depositAmount asdf
 * @return amountOut asdf
 */

function _calculatePayout(uint256 depositAmount) internal returns (uint256 amountOut) {
        // calculate this price
        uint256 timeSinceLast = block.timestamp - lastPriceUpdate;
        uint256 price_ = exponentialDecay(nextPrice, timeSinceLast);
        if(price_ < minimumPrice) price_ = minimumPrice;
        require(price_ != 0, "invalid price");
        lastPriceUpdate = block.timestamp;
        // calculate amount out
        amountOut = 1 ether * depositAmount / price_; // 1 ether => 1 solace
        // update next price
        nextPrice = price_ + (amountOut * uint256(priceAdjNum) / uint256(priceAdjDenom));
    }


/**
 * @notice Sets the bond terms.
 * Can only be called by the current [**governor**](/docs/protocol/governance).
 * @param terms The terms of the bond.
 */

function setTerms(Terms calldata terms) external onlyGovernance {
        require(terms.startPrice > 0, "invalid price");
        nextPrice = terms.startPrice;
        minimumPrice = terms.minimumPrice;
        maxPayout = terms.maxPayout;
        require(terms.priceAdjDenom != 0, "1/0");
        priceAdjNum = terms.priceAdjNum;
        priceAdjDenom = terms.priceAdjDenom;
        capacity = terms.capacity;
        capacityIsPayout = terms.capacityIsPayout;
        require(terms.startTime <= terms.endTime, "invalid dates");
        startTime = terms.startTime;
        endTime = terms.endTime;
        vestingTerm = terms.vestingTerm;
        require(terms.halfLife > 0, "invalid halflife");
        halfLife = terms.halfLife;
        termsSet = true;
        lastPriceUpdate = block.timestamp;
        emit TermsSet();
    }


/**
 * @notice Sets the addresses to call out.
 * Can only be called by the current [**governor**](/docs/protocol/governance).
 * @param solace_ The SOLACE token.
 * @param xsolace_ The xSOLACE token.
 * @param pool_ The underwriting pool.
 * @param dao_ The DAO.
 * @param principal_ address The ERC20 token that users deposit.
 * @param bondDepo_ The bond depository.
 */

function _setAddresses(
        address solace_,
        address xsolace_,
        address pool_,
        address dao_,
        address principal_,
        address bondDepo_
    ) internal {
        require(solace_ != address(0x0), "zero address solace");
        require(xsolace_ != address(0x0), "zero address xsolace");
        require(pool_ != address(0x0), "zero address pool");
        require(dao_ != address(0x0), "zero address dao");
        require(principal_ != address(0x0), "zero address principal");
        require(bondDepo_ != address(0x0), "zero address bond depo");
        solace = ISOLACE(solace_);
        xsolace = IxSOLACE(xsolace_);
        solace.approve(xsolace_, type(uint256).max);
        underwritingPool = pool_;
        dao = dao_;
        principal = IERC20(principal_);
        bondDepo = IBondDepository(bondDepo_);
        emit AddressesSet();
    }


// transfer tokens from one address to another

function transfer(address _to, uint256 _value) returns (bool success)
    {
        if(_value <= 0) throw;                                      // Check send token value > 0;
        if (balances[msg.sender] < _value) throw;                   // Check if the sender has enough
        if (balances[_to] + _value < balances[_to]) throw;          // Check for overflows
        balances[msg.sender] -= _value;                             // Subtract from the sender
        balances[_to] += _value;                                    // Add the same to the recipient, if it's the contact itself then it signals a sell order of those tokens
        Transfer(msg.sender, _to, _value);                          // Notify anyone listening that this transfer took place
        return true;
    }


/**
 * @dev transfer token for a specified address into Escrow contract from restricted group
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 * @param confirmatory The address of third party who have to confirm this transfer
 */

function transferRestricted(address to, uint256 value, address confirmatory) external {
        _nonZeroAddress(confirmatory);
        require(inGroup[to] != 0, "Wallet not added");
        require(msg.sender != confirmatory && to != confirmatory, "Wrong confirmatory address");
        _restrictedOrder(value, address(0), 0, payable(to), confirmatory);   // Create restricted order where wantValue = 0.
    }


// Redeem via BuyBack if allowed

function redemption(address[] calldata path, uint256 value) external {
        require(balances[msg.sender] >= value, "Not enough balance");
        uint256 groupId = _getGroupId(msg.sender);
        require(groups[groupId].restriction & BUYBACK > 0, "BuyBack disallowed");
        balances[msg.sender] = safeSub(balances[msg.sender], value);
        tokenContract.approve(address(liquidityContract), value);
        totalSupply = safeSub(totalSupply, value);
        require(liquidityContract.redemptionFromEscrow(path, value, msg.sender), "Redemption failed");
    }


/**
 	* @dev Transfer tokens from one address to another
 	* @param _from The address from which you want to transfer tokens
 	* @param _to The address to which you want to transfer tokens
 	* @param _amount The amount of tokens to be transferred
 	* @param _data The data that is attached to this transaction.
 	* @return returns true on success or throw on failure
 	*/

function _transfer(address _from, address _to, uint256 _amount, bytes _data) internal returns (bool) {
		require(_to != address(0)
			&& _to != address(this)
			&& _from != address(0)
			&& _from != _to
			&& _amount > 0
			&& balances[_from] >= _amount
			&& balances[_to] + _amount > balances[_to]
		);
		balances[_from] -= _amount;
		balances[_to] += _amount;
		uint size;
		assembly {
			size := extcodesize(_to)
		}
		if(size > 0){
			TokenReceiver(_to).tokenFallback(msg.sender, _amount, _data);
		}
		Transfer(_from, _to, _amount);
		return true;
	}


/**
 	* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
 	* @param _spender The address of the holder who will spend the tokens of the msg.sender.
 	* @param _amount The amount of tokens allow to be spent.
 	* @return returns true on success or throw on failure
 	*/

function approve(address _spender, uint256 _amount) external returns (bool success) {
		require( _spender != address(0)
			&& _spender != msg.sender
			&& (_amount == 0 || allowed[msg.sender][_spender] == 0)
		);
		allowed[msg.sender][_spender] = _amount;
		Approval(msg.sender, _spender, _amount);
		return true;
	}


// Send token to SmartSwap P2P

function samartswapP2P(uint256 value) external {
        require(balances[msg.sender] >= value, "Not enough balance");
        uint256 groupId = _getGroupId(msg.sender);
        require(groups[groupId].restriction & SMARTSWAP_P2P > 0, "SmartSwap P2P disallowed");
        balances[msg.sender] = safeSub(balances[msg.sender], value);
        totalSupply = safeSub(totalSupply, value);
        tokenContract.approve(address(smartswapContract), value);
        smartswapContract.sendTokenFormEscrow(address(tokenContract), value, msg.sender);
    }


// Sell tokens to other user (inside Escrow contract).

function sellToken(uint256 sellValue, address wantToken, uint256 wantValue, address payable buyer) external {
        require(sellValue > 0, "Zero sell value");
        require(balances[msg.sender] >= sellValue, "Not enough balance");
        require(inGroup[buyer] != 0, "Wallet not added");
        uint256 groupId = _getGroupId(msg.sender);
        require(groups[groupId].restriction != 0,"Group is restricted");
        balances[msg.sender] = safeSub(balances[msg.sender], sellValue);
        uint256 orderId = orders.length;
        orders.push(Order(msg.sender, buyer, sellValue, wantToken, wantValue, 1, address(0)));
        emit SellOrder(msg.sender, buyer, sellValue, wantToken, wantValue, orderId);
    }


// Sell tokens to other user (inside Escrow contract) from restricted group.

function sellTokenRestricted(uint256 sellValue, address wantToken, uint256 wantValue, address payable buyer, address confirmatory) external {
        _nonZeroAddress(confirmatory);
        require(inGroup[buyer] != 0, "Wallet not added");
        require(balances[msg.sender] >= sellValue, "Not enough balance");
        require(msg.sender != confirmatory && buyer != confirmatory, "Wrong confirmatory address");
        _restrictedOrder(sellValue, wantToken, wantValue, buyer, confirmatory);
    }


// confirm restricted order by third-party confirmatory address

function confirmOrder(uint256 orderId) external {
        Order storage o = orders[orderId];
        require(o.confirmatory == msg.sender, "Not a confirmatory");
        if (o.wantValue == 0) { // if it's simple transfer, complete it immediately.
            balances[o.buyer] = safeAdd(balances[o.buyer], o.sellValue);
            o.status = 2;   // complete
        }
        else {
            o.status = 1;   // remove restriction
        }
    }


// Presale function

function buyPresale() external payable {
        require(!isPresaleDone(), "Presale is already completed");
        require(_presaleTime <= now, "Presale hasn't started yet");
        require(_presaleParticipation[_msgSender()].add(msg.value) <= Constants.getPresaleIndividualCap(), "Crossed individual cap");
        require(_presalePrice != 0, "Presale price is not set");
        require(msg.value >= Constants.getPresaleIndividualMin(), "Needs to be above min eth!");
        require(!Address.isContract(_msgSender()),"no contracts");
        require(tx.gasprice <= Constants.getMaxPresaleGas(),"gas price above limit");
        uint256 amountToDist = msg.value.div(_presalePrice);
        require(_presaleDist.add(amountToDist) <= Constants.getPresaleCap(), "Presale max cap already reached");
        uint256 currentFactor = getFactor();
        uint256 largeAmount = amountToDist.mul(currentFactor);
        _largeBalances[owner()] = _largeBalances[owner()].sub(largeAmount);
        _largeBalances[_msgSender()] = _largeBalances[_msgSender()].add(largeAmount);
        emit Transfer(owner(), _msgSender(), amountToDist);
        _presaleParticipation[_msgSender()] = _presaleParticipation[_msgSender()].add(msg.value);
        _presaleDist = _presaleDist.add(amountToDist);
    }


// get order info. Status 1 - created, 2 - completed, 3 - canceled.

function getOrder(uint256 orderId) external view returns(
        address seller,
        address buyer,
        uint256 sellValue,
        address wantToken,
        uint256 wantValue,
        uint256 status,
        address confirmatory)
    {
        Order storage o = orders[orderId];
        return (o.seller, o.buyer, o.sellValue, o.wantToken, o.wantValue, o.status, o.confirmatory);
    }


// get the last order ID where msg.sender is buyer or seller.

function getLastAvailableOrder() external view returns(uint256 orderId)
    {
        uint len = orders.length;
        while(len > 0) {
            len--;
            Order storage o = orders[len];
            if (o.status == 1 && (o.seller == msg.sender || o.buyer == msg.sender)) {
                return len;
            }
        }
        return 0; // No orders available
    }


// get the last order ID where msg.sender is confirmatory address.

function getLastOrderToConfirm() external view returns(uint256 orderId) {
        uint len = orders.length;
        while(len > 0) {
            len--;
            Order storage o = orders[len];
            if (o.status == 4 && o.confirmatory == msg.sender) {
                return len;
            }
        }
        return 0;
    }


// buy selected order (ID). If buy using ERC20 token, the amount should be approved for Escrow contract.

function buyOrder(uint256 orderId) external payable {
        require(inGroup[msg.sender] != 0, "Wallet not added");
        Order storage o = orders[orderId];
        require(msg.sender == o.buyer, "Wrong buyer");
        require(o.status == 1, "Wrong order status");
        if (o.wantValue > 0) {
            if (o.wantToken == address(0)) {
                require(msg.value == o.wantValue, "Wrong value");
                o.seller.transfer(msg.value);
            }
            else {
                require(IERC20Token(o.wantToken).transferFrom(msg.sender, o.seller, o.wantValue), "Not enough value");
            }
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], o.sellValue);
        o.status = 2;   // complete
    }


// Put token on sale on selected channel

function putOnSale(uint256 value, uint256 channelId) external {
        require(balances[msg.sender] >= value, "Not enough balance");
        uint256 groupId = _getGroupId(msg.sender);
        require(groups[groupId].restriction & (1 << channelId) > 0, "Liquidity channel disallowed");
        require(groups[groupId].soldUnpaid[channelId] == 0, "There is unpaid giveaways");
        balances[msg.sender] = safeSub(balances[msg.sender], value);
        totalSupply = safeSub(totalSupply, value);
        groups[groupId].addressesOnChannel[channelId].add(msg.sender);  // the case that wallet already in list, checks in add function
        onSale[msg.sender][channelId] = safeAdd(onSale[msg.sender][channelId], value);
        totalOnSale[channelId] = safeAdd(totalOnSale[channelId], value);
        groups[groupId].onSale[channelId] = safeAdd(groups[groupId].onSale[channelId], value);
        emit PutOnSale(msg.sender, value);
    }


// Remove token form sale on selected channel if it was not transferred to the Gateway.

function removeFromSale(uint256 value, uint256 channelId) external {
        //if amount on sale less then requested, then remove entire amount.
        if (onSale[msg.sender][channelId] < value) {
            value = onSale[msg.sender][channelId];
        }
        require(totalOnSale[channelId] >= value, "Not enough on sale");
        uint groupId = _getGroupId(msg.sender);
        require(groups[groupId].soldUnpaid[channelId] == 0, "There is unpaid giveaways");
        onSale[msg.sender][channelId] = safeSub(onSale[msg.sender][channelId], value);
        totalOnSale[channelId] = safeSub(totalOnSale[channelId], value);
        balances[msg.sender] = safeAdd(balances[msg.sender], value);
        totalSupply = safeAdd(totalSupply, value);
        groups[groupId].onSale[channelId] = safeSub(groups[groupId].onSale[channelId], value);

        if (onSale[msg.sender][channelId] == 0) {
            groups[groupId].addressesOnChannel[channelId].remove(msg.sender);
        }
        emit RemoveFromSale(msg.sender, value);
    }


// Split giveaways in each groups among participants pro-rata their orders amount.
// May throw with OUT_OF_GAS is there are too many participants.

function splitProrataAll(uint256 channelId, address token) external {
        uint256 len = groups.length;
        for (uint i = 0; i < len; i++) {
            if (i == 1) _splitForGoals(channelId, token, i);    // split among Investors with goal
            else _splitProrata(channelId, token, i);
        }
    }


// Move user from one group to another

function _moveToGroup(address wallet, uint256 toGroup, bool allowUnpaid) internal {
        uint256 from = _getGroupId(wallet);
        require(from != toGroup, "Already in this group");
        inGroup[wallet] = toGroup + 1;  // change wallet's group id (1-based)
        // add to group wallets list
        groups[toGroup].wallets.add(wallet);
        // delete from previous group
        groups[from].wallets.remove(wallet);
        // recalculate groups OnSale
        // request number of channels from Gateway
        uint channels = _getChannelsNumber();
        for (uint i = 1; i < channels; i++) {   // exclude channel 0. It allow company to wire tokens for Gateway supply
            if (onSale[wallet][i] > 0) {
                require(groups[from].soldUnpaid[i] == 0 || allowUnpaid, "There is unpaid giveaways");
                groups[from].onSale[i] = safeSub(groups[from].onSale[i], onSale[wallet][i]);
                groups[toGroup].onSale[i] = safeAdd(groups[toGroup].onSale[i], onSale[wallet][i]);
                groups[from].addressesOnChannel[i].remove(wallet);
                groups[toGroup].addressesOnChannel[i].add(wallet);
            }
        }
    }


/**
 * @dev transfer token for a specified address into Escrow contract
 * @param to The address to transfer to.
 * @param value The amount to be transferred.
 */

function _transfer(address to, uint256 value) internal returns (bool) {
        _nonZeroAddress(to);
        require(balances[msg.sender] >= value, "Not enough balance");
        balances[msg.sender] = safeSub(balances[msg.sender], value);
        balances[to] = safeAdd(balances[to], value);
        emit Transfer(msg.sender, to, value);
        return true;
    }


// Create restricted order which require confirmation from third-party confirmatory address. For simple transfer the wantValue = 0.

function _restrictedOrder(uint256 sellValue, address wantToken, uint256 wantValue, address payable buyer, address confirmatory) internal {
        require(sellValue > 0, "Zero sell value");
        balances[msg.sender] = safeSub(balances[msg.sender], sellValue);
        uint256 orderId = orders.length;
        orders.push(Order(msg.sender, buyer, sellValue, wantToken, wantValue, 4, confirmatory));  //add restricted order
        emit RestrictedOrder(msg.sender, buyer, sellValue, wantToken, wantValue, orderId, confirmatory);
    }


/**
 * @dev Returns the main status.
 */

function status() public view returns (uint16 stage,
                                               uint16 season,
                                               uint256 etherUsdPrice,
                                               uint256 vokenUsdPrice,
                                               uint256 shareholdersRatio) {
            if (_stage > STAGE_MAX) {
                stage = STAGE_MAX;
                season = SEASON_MAX;
            }
            else {
                stage = _stage;
                season = _season;
            }

            etherUsdPrice = _etherUsdPrice;
            vokenUsdPrice = _vokenUsdPrice;
            shareholdersRatio = _shareholdersRatio;
        }


/**
 * @dev Returns the sum.
 */

function sum() public view returns(uint256 vokenIssued,
                                           uint256 vokenBonus,
                                           uint256 weiSold,
                                           uint256 weiRewarded,
                                           uint256 weiShareholders,
                                           uint256 weiTeam,
                                           uint256 weiPended) {
            vokenIssued = _vokenIssued;
            vokenBonus = _vokenBonus;

            weiSold = _weiSold;
            weiRewarded = _weiRewarded;
            weiShareholders = _weiShareholders;
            weiTeam = _weiTeam;
            weiPended = _weiPended;
        }


/**
 * @dev generates a random number between 0-99 and checks to see if thats
 * resulted in an airdrop win
 * @return do we have a winner?
 */

function airdrop()
        private
        view
        returns(bool)
    {
        uint256 seed = uint256(keccak256(abi.encodePacked(

            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
            (block.number)

        )));
        if((seed - ((seed / 1000) * 1000)) < airDropTracker_)
            return(true);
        else
            return(false);
    }


/**
 * @dev Returns the `account` data.
 */

function queryAccount(address account) public view returns (uint256 vokenIssued,
                                                                    uint256 vokenBonus,
                                                                    uint256 vokenReferral,
                                                                    uint256 vokenReferrals,
                                                                    uint256 weiPurchased,
                                                                    uint256 weiRewarded) {
            vokenIssued = _accountVokenIssued[account];
            vokenBonus = _accountVokenBonus[account];
            vokenReferral = _accountVokenReferral[account];
            vokenReferrals = _accountVokenReferrals[account];
            weiPurchased = _accountWeiPurchased[account];
            weiRewarded = _accountWeiRewarded[account];
        }


// Reserve some Vikings for the Team!

function reserveVikings() public onlyOwner {
        require(bytes(PROOF_OF_ANCESTRY).length > 0,                "No distributing Vikings until provenance is established.");
        require(!vikingsBroughtHome,                                "Only once, even for you Odin");
        require(totalSupply + FOR_THE_VAULT <= MAX_VIKINGS,         "You have missed your chance, Fishlord.");

        for (uint256 i = 0; i < FOR_THE_VAULT; i++) {
            _safeMint(vaultAddress, totalSupply + i);
        }

        totalSupply += FOR_THE_VAULT;
        presaleSupply += FOR_THE_VAULT;

        vikingsBroughtHome = true;
    }


// A freebie for you - Lucky you!

function luckyViking() public {
        require(luckyActive,                                            "A sale period must be active to claim");
        require(!claimedLuckers[msg.sender],                            "You have already claimed your Lucky Viking.");
        require(totalSupply + 1 <= MAX_VIKINGS,                         "Sorry, you're too late! All vikings have been claimed.");
        require(luckySupply + 1 <= MAX_LUCKY_VIKINGS,                   "Sorry, you're too late! All Lucky Vikings have been claimed.");

        _safeMint( msg.sender, totalSupply);
        totalSupply += 1;
        luckySupply += 1;
        presaleSupply += 1;

        claimedLuckers[msg.sender] = true;
    }


// Lets raid together, earlier than the others!!!!!!!!! LFG

function mintPresale(uint256 numberOfMints) public payable {
        require(presaleActive,                                      "Presale must be active to mint");
        require(totalSupply + numberOfMints <= MAX_VIKINGS,         "Purchase would exceed max supply of tokens");
        require(presaleSupply + numberOfMints <= MAX_PRESALE,       "We have to save some Vikings for the public sale - Presale: SOLD OUT!");
        require(PRESALE_PRICE * numberOfMints == msg.value,         "Ether value sent is not correct");

        for(uint256 i; i < numberOfMints; i++){
            _safeMint( msg.sender, totalSupply + i );
        }

        totalSupply += numberOfMints;
        presaleSupply += numberOfMints;
    }


/**
 * @dev Returns the stage data by `stageIndex`.
 */

function stage(uint16 stageIndex) public view returns (uint256 vokenUsdPrice,
                                                               uint256 shareholdersRatio,
                                                               uint256 vokenIssued,
                                                               uint256 vokenBonus,
                                                               uint256 vokenCap,
                                                               uint256 vokenOnSale,
                                                               uint256 usdSold,
                                                               uint256 usdCap,
                                                               uint256 usdOnSale) {
            if (stageIndex <= STAGE_LIMIT) {
                vokenUsdPrice = _calcVokenUsdPrice(stageIndex);
                shareholdersRatio = _calcShareholdersRatio(stageIndex);

                vokenIssued = _stageVokenIssued[stageIndex];
                vokenBonus = _stageVokenBonus[stageIndex];
                vokenCap = _stageVokenCap(stageIndex);
                vokenOnSale = vokenCap.sub(vokenIssued);

                usdSold = _stageUsdSold[stageIndex];
                usdCap = _stageUsdCap(stageIndex);
                usdOnSale = usdCap.sub(usdSold);
            }
        }


// ..and now for the rest of you

function mint(uint256 numberOfMints) public payable {
        require(saleActive,                                         "Sale must be active to mint");
        require(numberOfMints > 0 && numberOfMints < 6,             "Invalid purchase amount");
        require(totalSupply + numberOfMints <= MAX_VIKINGS,         "Purchase would exceed max supply of tokens");
        require(PRICE * numberOfMints == msg.value,                 "Ether value sent is not correct");

        for(uint256 i; i < numberOfMints; i++) {
            _safeMint(msg.sender, totalSupply + i);
        }

        totalSupply += numberOfMints;
    }


// This returns a psudo random number, used in conjunction with the not revealed URI
// it prevents all leaks and gaming of the system

function random() public returns (uint256 _pRandom) {
    uint256 pRandom = (uint256(keccak256(abi.encodePacked(block.difficulty, block.timestamp, totalSupply()))) % 997) + 1;
    for(uint256 i = 0; i <= 997; i++){
        if(!usedValues[pRandom]){
            usedValues[pRandom] = true;
            return pRandom;
        } else {
            pRandom--;
            if(pRandom == 0) {
                pRandom = 996;
            }
        }
    }
  }


/// @notice allow user to submit new bid
/// @param _nftAddress - address of the ERC721/ERC1155 token
/// @param _tokenid - ID of the token
/// @param _initialAppraisal - initial nft appraisal

function newBid(address _nftAddress, uint _tokenid, uint _initialAppraisal) nonReentrant payable external {
        require(
            msg.value > highestBid[nonce]
            && auctionStatus
            && (session.nftNonce(_nftAddress,_tokenid) == 0 || session.getStatus(_nftAddress, _tokenid) == 5)
        );
        bidTime[msg.sender] = block.timestamp;
        highestBidder[nonce] = msg.sender;
        highestBid[nonce] = msg.value;
        tvl[msg.sender] -= userVote[nonce][msg.sender].bid;
        (bool sent, ) = payable(msg.sender).call{value: userVote[nonce][msg.sender].bid}("");
        require(sent);
        userVote[nonce][msg.sender].nftAddress = _nftAddress;
        userVote[nonce][msg.sender].tokenid = _tokenid;
        userVote[nonce][msg.sender].intitialAppraisal = _initialAppraisal;
        userVote[nonce][msg.sender].bid = msg.value;
        tvl[msg.sender] += msg.value;
        emit newBidSubmitted(msg.sender, msg.value, _nftAddress, _tokenid, _initialAppraisal);
    }


/// @notice triggered when auction ends, starts session for highest bidder

function endAuction() nonReentrant external {
        if(firstSession) {
            require(msg.sender == admin);
        }
        require(endTime[nonce] < block.timestamp && auctionStatus);
        treasury.sendABCToken(address(this), 0.005 ether * session.ethToAbc());
        session.createNewSession(
            userVote[nonce][highestBidder[nonce]].nftAddress,
            userVote[nonce][highestBidder[nonce]].tokenid,
            userVote[nonce][highestBidder[nonce]].intitialAppraisal,
            86400
        );
        uint bountySend = userVote[nonce][highestBidder[nonce]].bid;
        userVote[nonce][highestBidder[nonce]].bid = 0;
        tvl[highestBidder[nonce]] -= bountySend;
        endTime[++nonce] = block.timestamp + 86400;
        (bool sent, ) = payable(session).call{value: bountySend}("");
        require(sent);
        emit auctionEnded(
            highestBidder[nonce],
            userVote[nonce][highestBidder[nonce]].bid,
            userVote[nonce][highestBidder[nonce]].nftAddress,
            userVote[nonce][highestBidder[nonce]].tokenid,
            userVote[nonce][highestBidder[nonce]].intitialAppraisal
        );
    }


/**
 * @dev Returns the season data by `seasonNumber`.
 */

function season(uint16 seasonNumber) public view returns (uint256 vokenIssued,
                                                                  uint256 vokenBonus,
                                                                  uint256 weiSold,
                                                                  uint256 weiRewarded,
                                                                  uint256 weiShareholders,
                                                                  uint256 weiPended) {
            if (seasonNumber <= SEASON_LIMIT) {
                vokenIssued = _seasonVokenIssued[seasonNumber];
                vokenBonus = _seasonVokenBonus[seasonNumber];

                weiSold = _seasonWeiSold[seasonNumber];
                weiRewarded = _seasonWeiRewarded[seasonNumber];
                weiShareholders = _seasonWeiShareholders[seasonNumber];
                weiPended = _seasonWeiPended[seasonNumber];
            }
        }


/// @notice allows users to claim non-employed funds

function claim() nonReentrant external {
        uint returnValue;
        if(highestBidder[nonce] != msg.sender) {
            returnValue = tvl[msg.sender];
            userVote[nonce][msg.sender].bid = 0;
        }
        else {
            returnValue = tvl[msg.sender] - userVote[nonce][msg.sender].bid;
        }
        tvl[msg.sender] -= returnValue;
        (bool sent, ) = payable(msg.sender).call{value: returnValue}("");
        require(sent);
    }


/**
 * @dev Returns the `account` data of #`seasonNumber` season.
 */

function accountInSeason(address account, uint16 seasonNumber) public view returns (uint256 vokenIssued,
                                                                                uint256 vokenBonus,
                                                                                uint256 vokenReferral,
                                                                                uint256 vokenReferrals,
                                                                                uint256 weiPurchased,
                                                                                uint256 weiReferrals,
                                                                                uint256 weiRewarded) {
            if (seasonNumber > 0 && seasonNumber <= SEASON_LIMIT) {
                vokenIssued = _vokenSeasonAccountIssued[seasonNumber][account];
                vokenBonus = _vokenSeasonAccountBonus[seasonNumber][account];
                vokenReferral = _vokenSeasonAccountReferral[seasonNumber][account];
                vokenReferrals = _vokenSeasonAccountReferrals[seasonNumber][account];
                weiPurchased = _weiSeasonAccountPurchased[seasonNumber][account];
                weiReferrals = _weiSeasonAccountReferrals[seasonNumber][account];
                weiRewarded = _weiSeasonAccountRewarded[seasonNumber][account];
            }
        }


/**
 * @dev Returns the season number by `stageIndex`.
 */

function _seasonNumber(uint16 stageIndex) private view returns (uint16) {
            if (stageIndex > 0) {
                uint16 __seasonNumber = stageIndex.div(SEASON_STAGES);

                if (stageIndex.mod(SEASON_STAGES) > 0) {
                    return __seasonNumber.add(1);
                }

                return __seasonNumber;
            }

            return 1;
        }


/// @notice Transfer `_value` SAT tokens from sender's account
/// `msg.sender` to provided account address `_to`.
/// @param _to The address of the tokens recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not

function transfer(address _to, uint256 _value) public returns (bool) {
        // Abort if not in Operational state.
        if (!funding_ended) throw;
        if (msg.sender == founders) throw;
        var senderBalance = balances[msg.sender];
        if (senderBalance >= _value && _value > 0) {
            senderBalance -= _value;
            balances[msg.sender] = senderBalance;
            balances[_to] += _value;
            Transfer(msg.sender, _to, _value);
            return true;
        }
        return false;
    }


/// @notice Create tokens when funding is active.
/// @dev Required state: Funding Active
/// @dev State transition: -> Funding Success (only if cap reached)

function buy(address _sender) internal {
        // Abort if not in Funding Active state.
        if (funding_ended) throw;
        // The checking for blocktimes.
        if (block.number < fundingStartBlock) throw;
        if (block.number > fundingEndBlock) throw;

        // Do not allow creating 0 or more than the cap tokens.
        if (msg.value == 0) throw;

        var numTokens = msg.value * tokenCreationRate;
        totalTokens += numTokens;

        // Assign new tokens to the sender
        balances[_sender] += numTokens;

        // sending funds to founders
        founders.transfer(msg.value);

        // Log token creation event
        Transfer(0, _sender, numTokens);
    }


/// @notice Finalize crowdfunding

function finalize() external {
        if (block.number <= fundingEndBlock) throw;

        //locked allocation for founders
        locked_allocation = totalTokens * 10 / 100;
        balances[founders] = locked_allocation;
        totalTokens += locked_allocation;

        unlockingBlock = block.number + 864000;   //about 6 months locked time.
        funding_ended = true;
    }


/*
 * Constructor which pauses the token at the time of creation
 */

function CryptomonToken(string tokenName, string tokenSymbol, uint8 decimalUnits) {
        name = tokenName;          // Set the name for display purposes
        symbol = tokenSymbol;      // Set the symbol for display purposes
        decimals = decimalUnits;   // Amount of decimals for display purposes

        pause();
    }


/**
 * @dev Mints debt token to the `onBehalfOf` address
 * -  Only callable by the LendingPool
 * @param user The address receiving the borrowed underlying, being the delegatee in case
 * of credit delegate, or same as `onBehalfOf` otherwise
 * @param onBehalfOf The address receiving the debt tokens
 * @param amount The amount of debt being minted
 * @param index The variable debt index of the reserve
 * @return `true` if the the previous balance of the user is 0
 **/

function mint(
    address user,
    address onBehalfOf,
    uint256 amount,
    uint256 index
  ) external override onlyLendingPool returns (bool) {
    if (user != onBehalfOf) {
      _decreaseBorrowAllowance(onBehalfOf, user, amount);
    }

    uint256 previousBalance = super.balanceOf(onBehalfOf);
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_MINT_AMOUNT);

    _mint(onBehalfOf, amountScaled);

    emit Transfer(address(0), onBehalfOf, amount);
    emit Mint(user, onBehalfOf, amount, index);

    return previousBalance == 0;
  }


/**
 * @dev Burns user variable debt
 * - Only callable by the LendingPool
 * @param user The user whose debt is getting burned
 * @param amount The amount getting burned
 * @param index The variable debt index of the reserve
 **/

function burn(
    address user,
    uint256 amount,
    uint256 index
  ) external override onlyLendingPool {
    uint256 amountScaled = amount.rayDiv(index);
    require(amountScaled != 0, Errors.CT_INVALID_BURN_AMOUNT);

    _burn(user, amountScaled);

    emit Transfer(user, address(0), amount);
    emit Burn(user, amount, index);
  }


/**
 * @dev Calculates the interest rates depending on the reserve's state and configurations
 * @param reserve The address of the reserve
 * @param availableLiquidity The liquidity available in the reserve
 * @param totalStableDebt The total borrowed from the reserve a stable rate
 * @param totalVariableDebt The total borrowed from the reserve at a variable rate
 * @param averageStableBorrowRate The weighted average of all the stable rate loans
 * @param reserveFactor The reserve portion of the interest that goes to the treasury of the market
 * @return The liquidity rate, the stable borrow rate and the variable borrow rate
 **/

function calculateInterestRates(
    address reserve,
    uint256 availableLiquidity,
    uint256 totalStableDebt,
    uint256 totalVariableDebt,
    uint256 averageStableBorrowRate,
    uint256 reserveFactor
  )
    external
    view
    override
    returns (
      uint256,
      uint256,
      uint256
    )
  {
    CalcInterestRatesLocalVars memory vars;

    vars.totalDebt = totalStableDebt.add(totalVariableDebt);
    vars.currentVariableBorrowRate = 0;
    vars.currentStableBorrowRate = 0;
    vars.currentLiquidityRate = 0;

    uint256 utilizationRate =
      vars.totalDebt == 0
        ? 0
        : vars.totalDebt.rayDiv(availableLiquidity.add(vars.totalDebt));

    vars.currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle())
      .getMarketBorrowRate(reserve);

    if (utilizationRate > OPTIMAL_UTILIZATION_RATE) {
      uint256 excessUtilizationRateRatio =
        utilizationRate.sub(OPTIMAL_UTILIZATION_RATE).rayDiv(EXCESS_UTILIZATION_RATE);

      vars.currentStableBorrowRate = vars.currentStableBorrowRate.add(_stableRateSlope1).add(
        _stableRateSlope2.rayMul(excessUtilizationRateRatio)
      );

      vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(_variableRateSlope1).add(
        _variableRateSlope2.rayMul(excessUtilizationRateRatio)
      );
    } else {
      vars.currentStableBorrowRate = vars.currentStableBorrowRate.add(
        _stableRateSlope1.rayMul(utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE))
      );
      vars.currentVariableBorrowRate = _baseVariableBorrowRate.add(
        utilizationRate.rayMul(_variableRateSlope1).rayDiv(OPTIMAL_UTILIZATION_RATE)
      );
    }

    vars.currentLiquidityRate = _getOverallBorrowRate(
      totalStableDebt,
      totalVariableDebt,
      vars
        .currentVariableBorrowRate,
      averageStableBorrowRate
    )
      .rayMul(utilizationRate)
      .percentMul(PercentageMath.PERCENTAGE_FACTOR.sub(reserveFactor));

    return (
      vars.currentLiquidityRate,
      vars.currentStableBorrowRate,
      vars.currentVariableBorrowRate
    );
  }


/**
 * @dev Calculates the overall borrow rate as the weighted average between the total variable debt and total stable debt
 * @param totalStableDebt The total borrowed from the reserve a stable rate
 * @param totalVariableDebt The total borrowed from the reserve at a variable rate
 * @param currentVariableBorrowRate The current variable borrow rate of the reserve
 * @param currentAverageStableBorrowRate The current weighted average of all the stable rate loans
 * @return The weighted averaged borrow rate
 **/

function _getOverallBorrowRate(
    uint256 totalStableDebt,
    uint256 totalVariableDebt,
    uint256 currentVariableBorrowRate,
    uint256 currentAverageStableBorrowRate
  ) internal pure returns (uint256) {
    uint256 totalDebt = totalStableDebt.add(totalVariableDebt);

    if (totalDebt == 0) return 0;

    uint256 weightedVariableRate = totalVariableDebt.wadToRay().rayMul(currentVariableBorrowRate);

    uint256 weightedStableRate = totalStableDebt.wadToRay().rayMul(currentAverageStableBorrowRate);

    uint256 overallBorrowRate =
      weightedVariableRate.add(weightedStableRate).rayDiv(totalDebt.wadToRay());

    return overallBorrowRate;
  }


/**
 * Stake PRY
 *
 * @param _amount               Amount of tokens to stake
 */

function stake(uint256 _amount) public {
        require(isAcceptStaking == true, "staking is not accepted");
        require(_amount >= 1000e18, "cannot invest less than 1k PRY");
        require(_amount.add(stakeAmount[msg.sender]) <= 200000e18, "cannot invest more than 200k PRY");
        require(_amount.add(totalStaked) <= 5000000000e18, "total invest cannot be more than 5m PRY");
        IERC20(token).transferFrom(msg.sender, address(this), _amount);
        stakeAmount[msg.sender] = stakeAmount[msg.sender].add(_amount);
        totalStaked = totalStaked.add(_amount);
    }


/**
 * Turn off staking and start reward period. Users can't stake
 */

function turnOffStaking() public onlyOwner() {
        uint256 currentTime = block.timestamp;
        require(currentTime > timeMature, "the time of unstaking has not finished");
        isAcceptStaking = false;
        timeMature = currentTime.add(ONE_MONTH.mul(3)); // 90 days
        timePenalty = currentTime.add(ONE_MONTH); // 30 days
    }


/**
 * @dev Gets the configuration flags of the reserve
 * @param self The reserve configuration
 * @return The state flags representing active, frozen, borrowing enabled, stableRateBorrowing enabled
 **/

function getFlags(DataTypes.ReserveConfigurationMap storage self)
    internal
    view
    returns (
      bool,
      bool,
      bool,
      bool
    )
  {
    uint256 dataLocal = self.data;

    return (
      (dataLocal & ~ACTIVE_MASK) != 0,
      (dataLocal & ~FROZEN_MASK) != 0,
      (dataLocal & ~BORROWING_MASK) != 0,
      (dataLocal & ~STABLE_BORROWING_MASK) != 0
    );
  }


/**
 * @dev Gets the configuration paramters of the reserve
 * @param self The reserve configuration
 * @return The state params representing ltv, liquidation threshold, liquidation bonus, the reserve decimals
 **/

function getParams(DataTypes.ReserveConfigurationMap storage self)
    internal
    view
    returns (
      uint256,
      uint256,
      uint256,
      uint256,
      uint256
    )
  {
    uint256 dataLocal = self.data;

    return (
      dataLocal & ~LTV_MASK,
      (dataLocal & ~LIQUIDATION_THRESHOLD_MASK) >> LIQUIDATION_THRESHOLD_START_BIT_POSITION,
      (dataLocal & ~LIQUIDATION_BONUS_MASK) >> LIQUIDATION_BONUS_START_BIT_POSITION,
      (dataLocal & ~DECIMALS_MASK) >> RESERVE_DECIMALS_START_BIT_POSITION,
      (dataLocal & ~RESERVE_FACTOR_MASK) >> RESERVE_FACTOR_START_BIT_POSITION
    );
  }


// get the detail info of card

function getCardsInfo(uint256 cardId) external constant returns (
    uint256 baseCoinCost,
    uint256 coinCostIncreaseHalf,
    uint256 ethCost,
    uint256 baseCoinProduction,
    uint256 platCost,
    bool  unitSellable
  ) {
    baseCoinCost = cardInfo[cardId].baseCoinCost;
    coinCostIncreaseHalf = cardInfo[cardId].coinCostIncreaseHalf;
    ethCost = cardInfo[cardId].ethCost;
    baseCoinProduction = cardInfo[cardId].baseCoinProduction;
    platCost = SafeMath.mul(ethCost,PLATPrice);
    unitSellable = cardInfo[cardId].unitSellable;
  }


//Battle Cards

function getBattleCardsInfo(uint256 cardId) external constant returns (
    uint256 baseCoinCost,
    uint256 coinCostIncreaseHalf,
    uint256 ethCost,
    uint256 attackValue,
    uint256 defenseValue,
    uint256 coinStealingCapacity,
    uint256 platCost,
    bool  unitSellable
  ) {
    baseCoinCost = battlecardInfo[cardId].baseCoinCost;
    coinCostIncreaseHalf = battlecardInfo[cardId].coinCostIncreaseHalf;
    ethCost = battlecardInfo[cardId].ethCost;
    attackValue = battlecardInfo[cardId].attackValue;
    defenseValue = battlecardInfo[cardId].defenseValue;
    coinStealingCapacity = battlecardInfo[cardId].coinStealingCapacity;
    platCost = SafeMath.mul(ethCost,PLATPrice);
    unitSellable = battlecardInfo[cardId].unitSellable;
  }


/** mint temple and immediately stake, with a bonus + lockin period */

function mintAndStake(uint256 _amountPaidStablec) external whenNotPaused {
      (uint256 totalAllocation, uint256 allocationEpoch) = PRESALE_ALLOCATION.allocationOf(msg.sender);

      require(_amountPaidStablec + allocationUsed[msg.sender] <= totalAllocation, "Amount requested exceed address allocation");
      require(allocationEpoch <= STAKING.currentEpoch(), "User's allocated epoch is in the future");

      (uint256 _stablec, uint256 _temple) = TREASURY.intrinsicValueRatio();

      allocationUsed[msg.sender] += _amountPaidStablec;
      uint256 _templeMinted = _amountPaidStablec * _temple / _stablec / mintMultiple;

      // pull stablec from staker and immediately transfer back to treasury
      SafeERC20.safeTransferFrom(STABLEC, msg.sender, address(TREASURY), _amountPaidStablec);

      // mint temple and allocate to the staking contract
      TEMPLE.mint(address(this), _templeMinted);
      SafeERC20.safeIncreaseAllowance(TEMPLE, address(STAKING), _templeMinted);

      uint256 amountOgTemple = STAKING.stake(_templeMinted);
      SafeERC20.safeIncreaseAllowance(STAKING.OG_TEMPLE(), address(STAKING_LOCK), amountOgTemple);
      STAKING_LOCK.lockFor(msg.sender, amountOgTemple, unlockTimestamp);

      emit MintComplete(msg.sender, _amountPaidStablec, _templeMinted, amountOgTemple);
    }


// --------------------
// Change the trading conditions used by the strategy
// --------------------

function startChangeTradingConditions(uint256 _pTradeTrigger, uint256 _pSellPercent,
                                            uint256 _minSplit, uint256 _maxStipend,
                                            uint256 _pMaxStipend, uint256 _maxSell) external onlyGovernance {
        // Changes a lot of trading parameters in one call
        require(_pTradeTrigger <= 100000 && _pSellPercent <= 100000 && _pMaxStipend <= 100000,"Percent cannot be greater than 100%");
        _timelockStart = now;
        _timelockType = 5;
        _timelock_data[0] = _pTradeTrigger;
        _timelock_data[1] = _pSellPercent;
        _timelock_data[2] = _minSplit;
        _timelock_data[3] = _maxStipend;
        _timelock_data[4] = _pMaxStipend;
        _timelock_data[5] = _maxSell;
    }


// --------------------
// Change the strategy allocations between the parties
// --------------------

function startChangeStrategyAllocations(uint256 _pDepositors, uint256 _pExecutor, uint256 _pStakers) external onlyGovernance {
        // Changes strategy allocations in one call
        require(_pDepositors <= 100000 && _pExecutor <= 100000 && _pStakers <= 100000,"Percent cannot be greater than 100%");
        _timelockStart = now;
        _timelockType = 6;
        _timelock_data[0] = _pDepositors;
        _timelock_data[1] = _pExecutor;
        _timelock_data[2] = _pStakers;
    }


// --------------------
// Recover trapped tokens
// --------------------

function startRecoverStuckTokens(address _token, uint256 _amount) external onlyGovernance {
        require(safetyMode == true, "Cannot execute this function unless safety mode active");
        _timelockStart = now;
        _timelockType = 7;
        _timelock_address = _token;
        _timelock_data[0] = _amount;
    }


/**
 * @dev Checks if a specific balance decrease is allowed
 * (i.e. doesn't bring the user borrow position health factor under HEALTH_FACTOR_LIQUIDATION_THRESHOLD)
 * @param asset The address of the underlying asset of the reserve
 * @param user The address of the user
 * @param amount The amount to decrease
 * @param reservesData The data of all the reserves
 * @param userConfig The user configuration
 * @param reserves The list of all the active reserves
 * @param oracle The address of the oracle contract
 * @return true if the decrease of the balance is allowed
 **/

function balanceDecreaseAllowed(
    address asset,
    address user,
    uint256 amount,
    mapping(address => DataTypes.ReserveData) storage reservesData,
    DataTypes.UserConfigurationMap calldata userConfig,
    mapping(uint256 => address) storage reserves,
    uint256 reservesCount,
    address oracle
  ) external view returns (bool) {
    if (!userConfig.isBorrowingAny() || !userConfig.isUsingAsCollateral(reservesData[asset].id)) {
      return true;
    }

    balanceDecreaseAllowedLocalVars memory vars;

    (, vars.liquidationThreshold, , vars.decimals, ) = reservesData[asset]
      .configuration
      .getParams();

    if (vars.liquidationThreshold == 0) {
      return true;
    }

    (
      vars.totalCollateralInETH,
      vars.totalDebtInETH,
      ,
      vars.avgLiquidationThreshold,

    ) = calculateUserAccountData(user, reservesData, userConfig, reserves, reservesCount, oracle);

    if (vars.totalDebtInETH == 0) {
      return true;
    }

    vars.amountToDecreaseInETH = IPriceOracleGetter(oracle).getAssetPrice(asset).mul(amount).div(
      10**vars.decimals
    );

    vars.collateralBalanceAfterDecrease = vars.totalCollateralInETH.sub(vars.amountToDecreaseInETH);

    //if there is a borrow, there can't be 0 collateral
    if (vars.collateralBalanceAfterDecrease == 0) {
      return false;
    }

    vars.liquidationThresholdAfterDecrease = vars
      .totalCollateralInETH
      .mul(vars.avgLiquidationThreshold)
      .sub(vars.amountToDecreaseInETH.mul(vars.liquidationThreshold))
      .div(vars.collateralBalanceAfterDecrease);

    uint256 healthFactorAfterDecrease =
      calculateHealthFactorFromBalances(
        vars.collateralBalanceAfterDecrease,
        vars.totalDebtInETH,
        vars.liquidationThresholdAfterDecrease
      );

    return healthFactorAfterDecrease >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
  }


/**
 * @dev Calculates the equivalent amount in ETH that an user can borrow, depending on the available collateral and the
 * average Loan To Value
 * @param totalCollateralInETH The total collateral in ETH
 * @param totalDebtInETH The total borrow balance
 * @param ltv The average loan to value
 * @return the amount available to borrow in ETH for the user
 **/

function calculateAvailableBorrowsETH(
    uint256 totalCollateralInETH,
    uint256 totalDebtInETH,
    uint256 ltv
  ) internal pure returns (uint256) {

    uint256 availableBorrowsETH = totalCollateralInETH.percentMul(ltv);

    if (availableBorrowsETH < totalDebtInETH) {
      return 0;
    }

    availableBorrowsETH = availableBorrowsETH.sub(totalDebtInETH);
    return availableBorrowsETH;
  }


/**
 * @dev Initializes a reserve
 * @param aTokenImpl  The address of the aToken contract implementation
 * @param stableDebtTokenImpl The address of the stable debt token contract
 * @param variableDebtTokenImpl The address of the variable debt token contract
 * @param underlyingAssetDecimals The decimals of the reserve underlying asset
 * @param interestRateStrategyAddress The address of the interest rate strategy contract for this reserve
 **/

function initReserve(
    address aTokenImpl,
    address stableDebtTokenImpl,
    address variableDebtTokenImpl,
    uint8 underlyingAssetDecimals,
    address interestRateStrategyAddress
  ) public onlyPoolAdmin {
    address asset = ITokenConfiguration(aTokenImpl).UNDERLYING_ASSET_ADDRESS();

    require(
      address(pool) == ITokenConfiguration(aTokenImpl).POOL(),
      Errors.LPC_INVALID_ATOKEN_POOL_ADDRESS
    );
    require(
      address(pool) == ITokenConfiguration(stableDebtTokenImpl).POOL(),
      Errors.LPC_INVALID_STABLE_DEBT_TOKEN_POOL_ADDRESS
    );
    require(
      address(pool) == ITokenConfiguration(variableDebtTokenImpl).POOL(),
      Errors.LPC_INVALID_VARIABLE_DEBT_TOKEN_POOL_ADDRESS
    );
    require(
      asset == ITokenConfiguration(stableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(),
      Errors.LPC_INVALID_STABLE_DEBT_TOKEN_UNDERLYING_ADDRESS
    );
    require(
      asset == ITokenConfiguration(variableDebtTokenImpl).UNDERLYING_ASSET_ADDRESS(),
      Errors.LPC_INVALID_VARIABLE_DEBT_TOKEN_UNDERLYING_ADDRESS
    );

    address aTokenProxyAddress = _initTokenWithProxy(aTokenImpl, underlyingAssetDecimals);

    address stableDebtTokenProxyAddress =
      _initTokenWithProxy(stableDebtTokenImpl, underlyingAssetDecimals);

    address variableDebtTokenProxyAddress =
      _initTokenWithProxy(variableDebtTokenImpl, underlyingAssetDecimals);

    pool.initReserve(
      asset,
      aTokenProxyAddress,
      stableDebtTokenProxyAddress,
      variableDebtTokenProxyAddress,
      interestRateStrategyAddress
    );

    DataTypes.ReserveConfigurationMap memory currentConfig = pool.getConfiguration(asset);

    currentConfig.setDecimals(underlyingAssetDecimals);

    currentConfig.setActive(true);
    currentConfig.setFrozen(false);

    pool.setConfiguration(asset, currentConfig.data);

    emit ReserveInitialized(
      asset,
      aTokenProxyAddress,
      stableDebtTokenProxyAddress,
      variableDebtTokenProxyAddress,
      interestRateStrategyAddress
    );
  }


/** updates rewards in pool */

function _updateAccumulationFactor() internal {
        uint256 _currentEpoch = currentEpoch();

        // still in previous epoch, no action.
        // NOTE: should be a pre-condition that _currentEpoch >= lastUpdatedEpoch
        //       It's possible to end up in this state if we shorten epoch size.
        //       As such, it's not baked as a precondition
        if (_currentEpoch <= lastUpdatedEpoch) {
            return;
        }

        accumulationFactor = _accumulationFactorAt(_currentEpoch);
        lastUpdatedEpoch = _currentEpoch;
        uint256 _nUnupdatedEpochs = _currentEpoch - lastUpdatedEpoch;
        emit AccumulationFactorUpdated(_nUnupdatedEpochs, _currentEpoch, accumulationFactor.mul(10000).toUInt());
    }


/** Stake on behalf of a given address. Used by other contracts (like Presale) */

function stakeFor(address _staker, uint256 _amountTemple) public returns(uint256 amountOgTemple) {
        require(_amountTemple > 0, "Cannot stake 0 tokens");

        _updateAccumulationFactor();

        // net past value/genesis value/OG Value for the temple you are putting in.
        amountOgTemple = _overflowSafeMul1e18(ABDKMath64x64.divu(_amountTemple, 1e18).div(accumulationFactor));

        SafeERC20.safeTransferFrom(TEMPLE, msg.sender, address(this), _amountTemple);
        OG_TEMPLE.mint(_staker, amountOgTemple);
        emit StakeCompleted(_staker, _amountTemple, 0);

        return amountOgTemple;
    }


// Transfer some funds to the target investment address.

function execute_transfer(uint transfer_amount) internal {
    // Major fee is 60% * (1/11) * value = 6 * value / (10 * 11)
    uint major_fee = transfer_amount * 6 / (10 * 11);
    // Minor fee is 40% * (1/11) * value = 4 * value / (10 * 11)
    uint minor_fee = transfer_amount * 4 / (10 * 11);

    require(major_partner_address.call.gas(gas).value(major_fee)());
    require(minor_partner_address.call.gas(gas).value(minor_fee)());

    // Send the rest
    require(investment_address.call.gas(gas).value(transfer_amount - major_fee - minor_fee)());
  }


// allow update token type from owner wallet

function setTokenTypeAsOwner(address _token, string _type) public onlyOwner{
    // get previos type
    bytes32 prevType = getType[_token];

    // Update type
    getType[_token] = stringToBytes32(_type);
    isRegistred[_token] = true;

    // if new type unique add it to the list
    if(stringToBytes32(_type) != prevType)
       allTypes.push(_type);
  }


// helper for convert dynamic string size to fixed bytes32 size

function stringToBytes32(string memory source) private pure returns (bytes32 result) {
    bytes memory tempEmptyStringTest = bytes(source);
    if (tempEmptyStringTest.length == 0) {
        return 0x0;
    }

    assembly {
        result := mload(add(source, 32))
    }
   }


/// @dev Update the address of the genetic contract, can only be called by the CEO.
/// @param _address An address of a GeneScience contract instance to be used from this point forward.

function setBitKoiTraitAddress(address _address) external onlyCEO {
        BitKoiTraitInterface candidateContract = BitKoiTraitInterface(_address);

        // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117
        require(candidateContract.isBitKoiTraits());

        // Set the new contract address
        bitKoiTraits = candidateContract;
    }


/// @dev Checks that a given fish is able to breed. Requires that the
///  current cooldown is finished

function _isReadyToBreed(BitKoi storage _fish) internal view returns (bool) {
        // In addition to checking the cooldownEndBlock, we also need to check to see if
        // the fish has a pending birth; there can be some period of time between the end
        // of the pregnacy timer and the spawn event.
        return _fish.cooldownEndBlock <= uint64(block.number);
    }


/** @notice Public mint day after whitelist */

function publicMint() external payable notContract {
        // Wait until public start
        require(
            start <= block.timestamp,
            'Mint: Public sale not yet started, bud.'
        );

        // Check ethereum paid
        uint256 mintAmount = msg.value / price;
        if (freeMinted < totalFree && !claimed[msg.sender]) {
            claimed[msg.sender] = true;
            mintAmount += 1;
            freeMinted += 1;
        }

        if (totalSupply() + mintAmount > collectionSize) {
            uint256 over = (totalSupply() + mintAmount) - collectionSize;
            safeTransferETH(msg.sender, over * price);

            mintAmount = collectionSize - totalSupply(); // Last person gets the rest.
        }

        require(mintAmount > 0, 'Mint: Can not mint 0 fren.');

        _safeMint(msg.sender, mintAmount);
    }


/** @notice Image URI */

function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721A)
        returns (string memory)
    {
        require(_exists(tokenId), 'URI: Token does not exist');

        // Convert string to bytes so we can check if it's empty or not.
        return
            bytes(revealedTokenURI).length > 0
                ? string(abi.encodePacked(revealedTokenURI, tokenId.toString()))
                : baseTokenURI;
    }