/**
 * @notice Low-level API to ensure the account spending period is always current
 * @dev | This method is internally called by `setAccountSpendingAmount()` to ensure
 * spending period is always the most current daily period.
 * @param self Internal storage proxying TokenIOStorage contract
 * @param account Ethereum address of the account holder
 * @return {"success" : "Returns true is successfully called from a contract"}
 */

function updateAccountSpendingPeriod(Data storage self, address account) internal returns (bool success) {
    uint begDate = getAccountSpendingPeriod(self, account);
    if (begDate > now) {
      return true;
    } else {
      uint duration = 86400; // 86400 seconds in a day
      require(
        setAccountSpendingPeriod(self, account, begDate.add(((now.sub(begDate)).div(duration).add(1)).mul(duration))),
        "Error: Unable to update account spending period.");

      return true;
    }
  }


/**
 * @notice Set the foreign currency exchange rate to USD in basis points
 * @dev | This value should always be relative to USD pair; e.g. JPY/USD, GBP/USD, etc.
 * @param self Internal storage proxying TokenIOStorage contract
 * @param currency The TokenIO currency symbol (e.g. USDx, JPYx, GBPx)
 * @param bpsRate Basis point rate of foreign currency exchange rate to USD
 * @return { "success": "Returns true if successfully called from another contract"}
 */

function setFxUSDBPSRate(Data storage self, string currency, uint bpsRate) internal returns (bool success) {
    bytes32 id = keccak256(abi.encodePacked('fx.usd.rate', currency));
    require(
      self.Storage.setUint(id, bpsRate),
      "Error: Unable to update account spending period.");

    return true;
  }


/**
 * @notice Return the foreign currency USD exchanged amount
 * @param self Internal storage proxying TokenIOStorage contract
 * @param currency The TokenIO currency symbol (e.g. USDx, JPYx, GBPx)
 * @param fxAmount Amount of foreign currency to exchange into USD
 * @return {"amount" : "Returns the foreign currency amount in USD"}
 */

function getFxUSDAmount(Data storage self, string currency, uint fxAmount) internal view returns (uint amount) {
    uint usdDecimals = getTokenDecimals(self, 'USDx');
    uint fxDecimals = getTokenDecimals(self, currency);
    /// @dev ensure decimal precision is normalized to USD decimals
    uint usdAmount = ((fxAmount.mul(getFxUSDBPSRate(self, currency)).div(10000)).mul(10**usdDecimals)).div(10**fxDecimals);
    return usdAmount;
  }


/**
 	 * @notice Set Fee Parameters for Fee Contract
 	 * @dev The min, max, flat transaction fees should be relative to decimal precision
 	 * @param feeBps Basis points transaction fee
 	 * @param feeMin Minimum transaction fees
 	 * @param feeMax Maximum transaction fee
 	 * @param feeFlat Flat transaction fee
 	 * returns {"success" : "Returns true if successfully called from another contract"}
 	 */

function setFeeParams(uint feeBps, uint feeMin, uint feeMax, uint feeFlat, bytes feeMsg) public onlyOwner returns (bool success) {
		require(lib.setFeeBPS(feeBps), "Error: Unable to set fee contract basis points.");
		require(lib.setFeeMin(feeMin), "Error: Unable to set fee contract minimum fee.");
		require(lib.setFeeMax(feeMax), "Error: Unable to set fee contract maximum fee.");
		require(lib.setFeeFlat(feeFlat), "Error: Unable to set fee contract flat fee.");
		require(lib.setFeeMsg(feeMsg), "Error: Unable to set fee contract default message.");
		return true;
	}


/**
 	 * @notice Transfer collected fees to another account; onlyOwner
 	 * @param  currency Currency symbol of the token (e.g. USDx, JYPx, GBPx)
 	 * @param  to 			Ethereum address of account to send token amount to
 	 * @param  amount	  Amount of tokens to transfer
 	 * @param  data		  Arbitrary bytes data message to include in transfer
 	 * @return {"success": "Returns ture if successfully called from another contract"}
 	 */

function transferCollectedFees(string currency, address to, uint amount, bytes data) public onlyOwner returns (bool success) {
		require(
			lib.forceTransfer(currency, address(this), to, amount, data),
			"Error: unable to transfer fees to account."
		);
		return true;
	}


/**
 * @dev Burns a specific wrapped punk
 */

function burn(uint256 punkIndex)
        public
        whenNotPaused
    {
        address sender = _msgSender();

        require(_isApprovedOrOwner(sender, punkIndex), "PunkWrapper: caller is not owner nor approved");

        _burn(punkIndex);

        // Transfers ownership of punk on original cryptopunk smart contract to caller
        _punkContract.transferPunk(sender, punkIndex);
    }


// -------------------------------------------------------------------------
// Public external interface
// -------------------------------------------------------------------------

function () external payable {
        // Distribute deposited Ether to all members related to their profit-share
        for (uint i=0; i<memberIndex.length; i++) {
            members[memberIndex[i]].unpaid =
                // Adding current deposit to members unpaid Wei amount
                members[memberIndex[i]].unpaid.add(
                    // MemberShare * DepositedWei / 100 = WeiAmount of member-share to be added to members unpaid holdings
                    members[memberIndex[i]].share.mul(msg.value).div(100)
                );
        }

        // Trigger 'Deposit'-event
        emit Deposit(msg.sender, msg.value);
    }


/// @notice create an Estate from a quad (a group of land forming a square on a specific grid in the Land contract)
/// @param sender address perforing the operation that will create an Estate from its land token
/// @param to the estate will belong to that address
/// @param size edge size of the quad, 3, 6, 12 or 24
/// @param x top left corner position of the quad
/// @param y top left corner position of the quad

function createFromQuad(
        address sender,
        address to,
        uint256 size,
        uint256 x,
        uint256 y
    ) external returns (uint256) {
        require(to != address(0), "DESTINATION_ZERO_ADDRESS");
        require(to != address(this), "DESTINATION_ESTATE_CONTRACT");
        _check_create_authorized(sender);
        uint256 estateId = _mintEstate(to);
        _addSingleQuad(sender, estateId, size, x, y, true, 0);
        return estateId;
    }


/// @notice create an Estate from a set of Lands, these need to be adjacent so they form a connected whole
/// @param sender address perforing the operation that will create an Estate from its land token
/// @param to the estate will belong to that address
/// @param ids set of Land to add to the estate
/// @param junctions list of indexes (the index at which the land/quad was indeed in the estate) that will connect added land to the current estate (only if the previously added land is not adjacent to the one added)

function createFromMultipleLands(
        address sender,
        address to,
        uint256[] calldata ids,
        uint256[] calldata junctions
    ) external returns (uint256) {
        require(to != address(0), "DESTINATION_ZERO_ADDRESS");
        require(to != address(this), "DESTINATION_ESTATE_CONTRACT");
        _check_create_authorized(sender);
        uint256 estateId = _mintEstate(to);
        _addLands(sender, estateId, ids, junctions);
        return estateId;
    }


/// @notice burn an Estate on behalf and transfer land
/// @param sender owner of the estate to be burnt
/// @param estateId estate id to be burnt
/// @param to address that will receive the lands

function burnAndTransferFrom(
        address sender,
        uint256 estateId,
        address to
    ) external {
        _check_burn_authorized(sender, estateId);
        _owners[estateId] = (_owners[estateId] & (2**255 - 1)) | (2**160);
        _numNFTPerAddress[sender]--;
        emit Transfer(sender, address(0), estateId);
        transferAllFromDestroyedEstate(sender, estateId, to);
    }


/// @notice transfer all lands from a burnt estate
/// @param sender previous owner of the burnt estate
/// @param estateId estate id
/// @param to address that will receive the lands

function transferAllFromDestroyedEstate(
        address sender,
        uint256 estateId,
        address to
    ) public {
        require(to != address(0), "DESTINATION_ZERO_ADDRESS");
        require(to != address(this), "DESTINATION_ESTATE_CONTRACT");
        _check_withdrawal_authorized(sender, estateId);
        uint24[] memory list = _quadsInEstate[estateId];
        uint256 num = list.length;
        require(num > 0, "WITHDRAWAL_COMPLETE");
        uint256[] memory sizes = new uint256[](num);
        uint256[] memory xs = new uint256[](num);
        uint256[] memory ys = new uint256[](num);
        for (uint256 i = 0; i < num; i++) {
            (uint16 x, uint16 y, uint8 size) = _decode(list[num - 1 - i]);
            _quadsInEstate[estateId].pop();
            sizes[i] = size;
            xs[i] = x;
            ys[i] = y;
        }
        delete _quadsInEstate[estateId];
        _land.batchTransferQuad(address(this), to, sizes, xs, ys, "");
        emit QuadsRemoved(estateId, num);
    }


/*only oracle can call this method*/

function __callback(bytes32 myid, string memory result)public payable {
        require (msg.sender == oraclize_cbAddress(), 'Only an oracle can call a method.');

        if(drunkard_game_hystory[myid].status){
            /* map random result to player */
            strings.slice memory oraclize_result = result.toSlice();
            strings.slice memory part;
            //serial number of request for random.org
            uint serilal = parseInt(oraclize_result.split(",".toSlice(), part).toString());
            //random number from random.org
            uint choices = parseInt(oraclize_result.split(",".toSlice(), part).toString());
            //game ID
            uint game = parseInt(oraclize_result.split(",".toSlice(), part).toString());

            if(game == GAME_DRUNKARD_ID){
                __drunkard_result(myid, serilal, choices);
            }
        }
   }


/*calculate winnings based on callback oraclize.io and random.org*/

function __drunkard_result(bytes32 _id, uint _serial, uint _random)internal {
            address payable player = drunkard_game_hystory[_id].from;
            uint choice = drunkard_game_hystory[_id].choice;
            uint bet = drunkard_game_hystory[_id].bet;
            bool winner = false;
            uint profit;

            if(choice == _random){
                winner = true;
                //calculation of winnings: bet + 125% - 3% ccommission
                uint start_profit = bet.mul(PERCENT_TO_WIN).div(100);
                //system commission 3%
                uint commission = start_profit.mul(MARKETING_PERCENT).div(100);
                profit = start_profit - commission;
                drunkard_game_hystory[_id].profit = profit;
                marketing_address.transfer(commission);
                player.transfer(profit);
            }else{
                drunkard_game_hystory[_id].profit = 0;
            }

            drunkard_game_hystory[_id].timestamp = now;
            drunkard_game_hystory[_id].game_choice = _random;
            drunkard_game_hystory[_id].winner = winner;
            drunkard_game_hystory[_id].status = false;
            drunkard_game_hystory[_id].serial = _serial;
            //delete the record of the pending user transaction
            playerPendingWithdrawals[player] = playerPendingWithdrawals[player].sub(bet);
            emit Drunkard(player, 1, choice, bet, _random, winner,  profit, _id);
   }


/*get game specific data*/

function getGame(bytes32 _game)public view returns(
       address from,
       bytes32 queryId,
       uint round,
       bool winner,
       uint bet,
       uint choice,
       uint game_choice,
       uint timestamp,
       uint profit,
       bool status,
       uint serial) {
        from = drunkard_game_hystory[_game].from;
        queryId = drunkard_game_hystory[_game].queryId;
        round = drunkard_game_hystory[_game].round;
        winner = drunkard_game_hystory[_game].winner;
        bet = drunkard_game_hystory[_game].bet;
        choice = drunkard_game_hystory[_game].choice;
        game_choice = drunkard_game_hystory[_game].game_choice;
        timestamp = drunkard_game_hystory[_game].timestamp;
        profit = drunkard_game_hystory[_game].profit;
        status = drunkard_game_hystory[_game].status;
        serial = drunkard_game_hystory[_game].serial;
    }


/**
 * @dev Returns the features for the specific crew member
 * @param _crewId The ERC721 tokenId for the crew member
 * @param _mod A modifier between 0 and 10,000
 */

function getFeatures(uint _crewId, uint _mod) public view returns (uint) {
    require(generatorSeed != "", "ArvadCitizenGenerator: seed not yet set");
    uint features = 0;
    uint mod = _mod;
    bytes32 crewSeed = getCrewSeed(_crewId);
    uint sex = generateSex(crewSeed);
    features |= sex << 8; // 2 bytes
    features |= generateBody(crewSeed, sex) << 10; // 16 bytes
    uint class = generateClass(crewSeed);
    features |= class << 26; // 8 bytes
    features |= generateArvadJob(crewSeed, class, mod) << 34; // 16 bytes
    features |= generateClothes(crewSeed, class) << 50; // 16 bytes to account for color variation
    features |= generateHair(crewSeed, sex) << 66; // 16 bytes
    features |= generateFacialFeatures(crewSeed, sex) << 82; // 16 bytes
    features |= generateHairColor(crewSeed) << 98; // 8 bytes
    features |= generateHeadPiece(crewSeed, class, mod) << 106; // 8 bytes
    return features;
  }


/**
 * @notice Will emit default URI log event for corresponding token _id
 * @param _tokenIDs Array of IDs of tokens to log default URI
 */

function _logURIs(uint256[] memory _tokenIDs) internal {
    string memory baseURL = baseMetadataURI;
    string memory tokenURI;

    for (uint256 i = 0; i < _tokenIDs.length; i++) {
      tokenURI = string(abi.encodePacked(baseURL, _uint2str(_tokenIDs[i]), ".json"));
      emit URI(tokenURI, _tokenIDs[i]);
    }
  }


// for another burn like 3.7 million or some more

function burnOf(uint256 tAmount) public {
        uint256 currentRate =  _getRate();
        uint256 rAmount = tAmount.mul(currentRate);

        // subtract additional burn from total supply
        _tTotal = _tTotal.sub(tAmount);

        // subtract additional burn from reflection supply
        _rTotal = _rTotal.sub(rAmount);

        emit Transfer(_msgSender(), address(0), tAmount);
    }


/**
 * @dev Overwrite ERC721 transferFrom with our specific needs
 * @notice This transfer has to be approved and then triggered by the _to
 * address in order to avoid sending unwanted pixels
 * @param _from Address sending token
 * @param _to Address receiving token
 * @param _tokenId ID of the transacting token
 * @param _price Price of the token being transfered
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 */

function transferFrom(address _from, address _to, uint256 _tokenId, uint256 _price, uint256 _x, uint256 _y)
    public
    auctionNotOngoing(_x, _y)
  {
    _subFromValueHeld(msg.sender, _price, false);
    _addToValueHeld(_to, _price);
    require(_to == msg.sender);
    Pixel memory pixel = pixelByCoordinate[_x][_y];

    super.transferFrom(_from, _to, _tokenId);
  }


/**
 * @dev Buys pixel blocks
 * @param _x X coordinates of the desired blocks
 * @param _y Y coordinates of the desired blocks
 * @param _price New prices of the pixel blocks
 * @param _contentData Data for the pixel
 */

function buyUninitializedPixelBlocks(uint256[] _x, uint256[] _y, uint256[] _price, bytes32[] _contentData)
    public
  {
    require(_x.length == _y.length && _x.length == _price.length && _x.length == _contentData.length);
    for (uint i = 0; i < _x.length; i++) {
      require(_price[i] > 0);
      _buyUninitializedPixelBlock(_x[i], _y[i], _price[i], _contentData[i]);
    }
  }


/**
 * Trigger a dutch auction
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 */

function beginDutchAuction(uint256 _x, uint256 _y)
    public
    auctionNotOngoing(_x, _y)
    validRange(_x, _y)
  {
    Pixel storage pixel = pixelByCoordinate[_x][_y];

    require(!userHasPositveBalance(pixel.seller));
    require(pixel.auctionId == 0);

    // Start a dutch auction
    pixel.auctionId = _generateDutchAuction(_x, _y);
    uint256 tokenId = _encodeTokenId(_x, _y);

    _updatePixelMapping(pixel.seller, _x, _y, pixel.price, pixel.auctionId, "");

    emit BeginDutchAuction(
      pixel.id,
      tokenId,
      pixel.auctionId,
      msg.sender,
      _x,
      _y,
      block.timestamp,
      block.timestamp.add(1 days)
    );
  }


/**
 * @dev Allow a user to bid in an auction
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 * @param _bid Desired bid of the user
 */

function bidInAuction(uint256 _x, uint256 _y, uint256 _bid)
    public
    validRange(_x, _y)
  {
    Pixel memory pixel = pixelByCoordinate[_x][_y];
    Auction storage auction = auctionById[pixel.auctionId];

    uint256 _tokenId = _encodeTokenId(_x, _y);
    require(pixel.auctionId != 0);
    require(auction.currentPrice < _bid);
    require(block.timestamp < auction.endTime);

    auction.currentPrice = _bid;
    auction.currentLeader = msg.sender;

    emit UpdateAuctionBid(
      pixel.id,
      _tokenId,
      auction.auctionId,
      msg.sender,
      _bid,
      block.timestamp
    );
  }


/**
 * End the auction
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 */

function endDutchAuction(uint256 _x, uint256 _y)
    public
    validRange(_x, _y)
  {
    Pixel memory pixel = pixelByCoordinate[_x][_y];
    Auction memory auction = auctionById[pixel.auctionId];

    require(pixel.auctionId != 0);
    require(auction.endTime < block.timestamp);

    // End dutch auction
    address winner = _endDutchAuction(_x, _y);
    _updatePixelMapping(winner, _x, _y, auction.currentPrice, 0, "");

    // Update user values
    _subFromValueHeld(pixel.seller, pixel.price, true);
    _addToValueHeld(winner, auction.currentPrice);

    uint256 tokenId = _encodeTokenId(_x, _y);
    removeTokenFrom(pixel.seller, tokenId);
    addTokenTo(winner, tokenId);
    emit Transfer(pixel.seller, winner, tokenId);

    emit EndDutchAuction(
      pixel.id,
      tokenId,
      winner,
      _x,
      _y
    );
  }


/**
 * @dev Buys an uninitialized pixel block for 0 ETH
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 * @param _price New price for the pixel
 * @param _contentData Data for the pixel
 */

function _buyUninitializedPixelBlock(uint256 _x, uint256 _y, uint256 _price, bytes32 _contentData)
    internal
    validRange(_x, _y)
    hasPositveBalance(msg.sender)
  {
    Pixel memory pixel = pixelByCoordinate[_x][_y];

    require(pixel.seller == address(0), "Pixel must not be initialized");

    uint256 tokenId = _encodeTokenId(_x, _y);
    bytes32 pixelId = _updatePixelMapping(msg.sender, _x, _y, _price, 0, _contentData);

    _addToValueHeld(msg.sender, _price);
    _mint(msg.sender, tokenId);

    emit BuyPixel(
      pixelId,
      address(0),
      msg.sender,
      _x,
      _y,
      _price,
      _contentData
    );
  }


/**
 * @dev Buys a pixel block
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 * @param _price New price of the pixel block
 * @param _currentValue Current value of the transaction
 * @param _contentData Data for the pixel
 */

function _buyPixelBlock(uint256 _x, uint256 _y, uint256 _price, uint256 _currentValue, bytes32 _contentData)
    internal
    validRange(_x, _y)
    hasPositveBalance(msg.sender)
    returns (uint256)
  {
    Pixel memory pixel = pixelByCoordinate[_x][_y];
    require(pixel.auctionId == 0);  // Stack to deep if this is a modifier
    uint256 _taxOnPrice = _calculateTax(_price);

    require(pixel.seller != address(0), "Pixel must be initialized");
    require(userBalanceAtLastPaid[msg.sender] >= _taxOnPrice);
    require(pixel.price <= _currentValue, "Must have sent sufficient funds");

    uint256 tokenId = _encodeTokenId(_x, _y);

    removeTokenFrom(pixel.seller, tokenId);
    addTokenTo(msg.sender, tokenId);
    emit Transfer(pixel.seller, msg.sender, tokenId);

    _addToValueHeld(msg.sender, _price);
    _subFromValueHeld(pixel.seller, pixel.price, false);

    _updatePixelMapping(msg.sender, _x, _y, _price, 0, _contentData);
    pixel.seller.transfer(pixel.price);

    emit BuyPixel(
      pixel.id,
      pixel.seller,
      msg.sender,
      _x,
      _y,
      pixel.price,
      _contentData
    );

    return _currentValue.sub(pixel.price);
  }


/**
 * @dev Set prices for a specific block
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 * @param _price New price of the pixel block
 */

function _setPixelBlockPrice(uint256 _x, uint256 _y, uint256 _price)
    internal
    auctionNotOngoing(_x, _y)
    validRange(_x, _y)
  {
    Pixel memory pixel = pixelByCoordinate[_x][_y];

    require(pixel.seller == msg.sender, "Sender must own the block");
    _addToValueHeld(msg.sender, _price);

    delete pixelByCoordinate[_x][_y];

    bytes32 pixelId = _updatePixelMapping(msg.sender, _x, _y, _price, 0, "");

    emit SetPixelPrice(
      pixelId,
      pixel.seller,
      _x,
      _y,
      pixel.price
    );
  }


/**
 * @dev Update pixel mapping every time it is purchase or the price is
 * changed
 * @param _seller Seller of the pixel block
 * @param _x X coordinate of the desired block
 * @param _y Y coordinate of the desired block
 * @param _price Price of the pixel block
 * @param _contentData Data for the pixel
 */

function _updatePixelMapping
  (
    address _seller,
    uint256 _x,
    uint256 _y,
    uint256 _price,
    bytes32 _auctionId,
    bytes32 _contentData
  )
    internal
    returns (bytes32)
  {
    bytes32 pixelId = keccak256(
      abi.encodePacked(
        _x,
        _y
      )
    );

    pixelByCoordinate[_x][_y] = Pixel({
      id: pixelId,
      seller: _seller,
      x: _x,
      y: _y,
      price: _price,
      auctionId: _auctionId,
      contentData: _contentData
    });

    return pixelId;
  }


/**
 * @dev Constructor that gives msg.sender all of existing tokens.
 * @param _company address reserve tokens (300000000)
 * @param _founders_1 address reserve tokens (300000000)
 * @param _founders_2 address reserve tokens (50000000)
 * @param _isPause bool (pause === true)
 */

function PAXToken(address _company, address _founders_1, address _founders_2, bool _isPause) public {
        require(_company != address(0) && _founders_1 != address(0) && _founders_2 != address(0));
        paused = _isPause;
        totalSupply = INITIAL_SUPPLY;
        balances[msg.sender] = 349500000 * (10 ** uint256(decimals));
        balances[_company] = 300000000 * (10 ** uint256(decimals));
        balances[_founders_1] = 300000000 * (10 ** uint256(decimals));
        balances[_founders_2] = 50000000 * (10 ** uint256(decimals));
        emit Transfer(0x0, msg.sender, balances[msg.sender]);
        emit Transfer(0x0, _company, balances[_company]);
        emit Transfer(0x0, _founders_1, balances[_founders_1]);
        emit Transfer(0x0, _founders_2, balances[_founders_2]);

    }


/**
 * @dev Manual sending tokens
 * @param _to address where sending tokens
 * @param _value uint256 value tokens for sending
 */

function manualSendTokens(address _to, uint256 _value) public onlyOwner returns(bool) {
        uint tokens = _value;
        uint avalibleTokens = token.balanceOf(this);

        if (tokens < avalibleTokens) {
            if (tokens <= stages[3].limit) {
                stages[3].limit = (stages[3].limit).sub(tokens);
            } else if (tokens <= (stages[3].limit).add(stages[2].limit)) {
                stages[2].limit = (stages[2].limit).sub(tokens.sub(stages[3].limit));
                stages[3].limit = 0;
            } else if (tokens <= (stages[3].limit).add(stages[2].limit).add(stages[1].limit)) {
                stages[1].limit = (stages[1].limit).sub(tokens.sub(stages[3].limit).sub(stages[2].limit));
                stages[3].limit = 0;
                stages[2].limit = 0;
            } else if (tokens <= (stages[3].limit).add(stages[2].limit).add(stages[1].limit).add(stages[0].limit)) {
                stages[0].limit = (stages[0].limit).sub(tokens.sub(stages[3].limit).sub(stages[2].limit).sub(stages[1].limit));
                stages[3].limit = 0;
                stages[2].limit = 0;
                stages[1].limit = 0;
            }
        } else {
            tokens = avalibleTokens;
            stages[3].limit = 0;
            stages[2].limit = 0;
            stages[1].limit = 0;
            stages[0].limit = 0;
        }

        sendingTokens = sendingTokens.add(tokens);
        sumWei = sumWei.add(tokens.mul(rate).div(decimals));
        totalSold = totalSold.add(tokens);
        token.ownersTransfer(_to, tokens);

        return true;
    }


/**
 * @dev Returns stage id
 */

function getStageId() public view returns(uint) {
        uint stageId;
        uint today = now;

        if (today < stages[0].stop) {
            stageId = 0;

        } else if (today >= stages[1].start &&
        today < stages[1].stop ) {
            stageId = 1;

        } else if (today >= stages[2].start &&
        today < stages[2].stop ) {
            stageId = 2;

        } else if (today >= stages[3].start &&
        today < stages[3].stop ) {
            stageId = 3;

        } else if (today >= stages[3].stop) {
            stageId = 4;

        } else {
            return 5;
        }

        uint tempId = (stageId > period) ? stageId : period;
        return tempId;
    }


/**
 * @dev Returns Limit of coins for the period and Number of coins taking
 * into account the bonus for the period
 */

function getStageData() public view returns(uint tempLimit, uint tempBonus) {
        uint stageId = getStageId();
        tempBonus = stages[stageId].bonus;

        if (stageId == 0) {
            tempLimit = stages[0].limit;

        } else if (stageId == 1) {
            tempLimit = (stages[0].limit).add(stages[1].limit);

        } else if (stageId == 2) {
            tempLimit = (stages[0].limit).add(stages[1].limit).add(stages[2].limit);

        } else if (stageId == 3) {
            tempLimit = (stages[0].limit).add(stages[1].limit).add(stages[2].limit).add(stages[3].limit);

        } else {
            tempLimit = token.balanceOf(this);
            tempBonus = typicalBonus;
            return;
        }
        tempLimit = tempLimit.sub(totalSold);
        return;
    }


/**
 * @dev Sending tokens to the recipient, based on the amount of ether that it sent
 * @param _etherValue uint Amount of sent ether
 * @param _to address The address which you want to transfer to
 */

function sendTokens(uint _etherValue, address _to) internal isUnderHardCap {
        uint limit;
        uint bonusCoefficient;
        (limit, bonusCoefficient) = getStageData();
        uint tokens = (_etherValue).mul(bonusCoefficient).mul(decimals).div(100);
        tokens = tokens.div(rate);
        bool needPause;

        if (tokens > limit) {
            needPause = true;
            uint stageEther = calculateStagePrice();
            period++;
            if (period == 4) {
                balances[msg.sender] = balances[msg.sender].add(stageEther);
                sumWei = sumWei.add(stageEther);
                token.ownersTransfer(_to, limit);
                totalSold = totalSold.add(limit);
                _to.transfer(_etherValue.sub(stageEther));
                state = false;
                return;
            }
            balances[msg.sender] = balances[msg.sender].add(stageEther);
            sumWei = sumWei.add(stageEther);
            token.ownersTransfer(_to, limit);
            totalSold = totalSold.add(limit);
            sendTokens(_etherValue.sub(stageEther), _to);

        } else {
            require(tokens <= token.balanceOf(this));
            if (limit.sub(tokens) < 500) {
                needPause = true;
                period++;
            }
            balances[msg.sender] = balances[msg.sender].add(_etherValue);
            sumWei = sumWei.add(_etherValue);
            token.ownersTransfer(_to, tokens);
            totalSold = totalSold.add(tokens);
        }

        if (needPause) {
            pausedByValue = true;
            usersPause();
        }
    }


/**
 * @dev Moving date after the pause
 * @param _shift uint Time in seconds
 */

function dateMove(uint _shift) private returns(bool) {
        require(_shift > 0);

        uint i;

        if (pausedByValue) {
            stages[period].start = now;
            stages[period].stop = (stages[period].start).add(stages[period].duration);

            for (i = period + 1; i < 4; i++) {
                stages[i].start = stages[i - 1].stop;
                stages[i].stop = (stages[i].start).add(stages[i].duration);
            }

        } else {
            if (manualPause) stages[period].stop = (stages[period].stop).add(_shift);

            for (i = period + 1; i < 4; i++) {
                stages[i].start = (stages[i].start).add(_shift);
                stages[i].stop = (stages[i].stop).add(_shift);
            }
        }

        emit DateMoved(_shift);

        return true;
    }


/**
 * @dev Set start date
 * @param _start uint Time start
 */

function setStartDate(uint _start) public onlyOwner returns(bool) {
        require(_start > now);
        require(requireOnce);

        stages[0].start = _start;
        stages[0].stop = _start.add(stages[0].duration);
        stages[1].start = stages[0].stop;
        stages[1].stop = stages[1].start.add(stages[1].duration);
        stages[2].start = stages[1].stop;
        stages[2].stop = stages[2].start.add(stages[2].duration);
        stages[3].start = stages[2].stop;
        stages[3].stop = stages[3].start.add(stages[3].duration);

        return true;
    }


/**
 * This function transfer `_usdtAmount` USDT to the smart contract
 * and calculate the amount of Lyn then send Lyn to the buyer
 */

function buy(uint256 _usdtAmount) onlyBuyable() external {
        // Calculate lyn amount
        uint256 lynAmount = _usdtAmount.mul(LYN_DECIMAL).div(marketPrice);

        // Transfer usdt to this contract
        usdtToken.transferFrom(msg.sender, address(this), _usdtAmount);

        // Transfer lyn to buyer
        lynToken.transfer(msg.sender, lynAmount);

        emit Buy(msg.sender, _usdtAmount, lynAmount);
    }


/* Constructor taking
 * resqueAccountHash: keccak256(address resqueAccount);
 * authorityAccount: address of authorityAccount that will set data for withdrawing to Emergency account
 * kwHash: keccak256("your keyword phrase");
 * photoHshs: array of keccak256(keccak256(data_of_yourphoto.pdf)) - hashes of photo files taken for this NYX Account.
 */

function NYX(bytes32 resqueAccountHash, address authorityAccount, bytes32 kwHash, bytes32[10] photoHshs) {
        owner = msg.sender;
        resqueHash = resqueAccountHash;
        authority = authorityAccount;
        keywordHash = kwHash;
        // save photo hashes as state forever
        uint8 x = 0;
        while(x < photoHshs.length)
        {
            photoHashes[x] = photoHshs[x];
            x++;
        }
    }


// Switch on/off Last Chance function

function toggleLastChance(bool useResqueAccountAddress) onlyByOwner()
	{
	    // Only allowed in normal stage to prevent changing this by stolen Owner's account
	    require(stage == Stages.Normal);
	    // Toggle Last Chance function flag
		lastChanceEnabled = !lastChanceEnabled;
		// If set to true knowing of Resque address (not key or password) will be required to use Last Chance function
		lastChanceUseResqueAccountAddress = useResqueAccountAddress;
	}


// Standard transfer Ether using Owner account

function transferByOwner(address recipient, uint amount) onlyByOwner() payable {
        // Only in Normal stage possible
        require(stage == Stages.Normal);
        // Amount must not exeed this.balance
        require(amount <= this.balance);
		// Require valid address to transfer
		require(recipient != address(0x0));

        recipient.transfer(amount);
        // This is used by Last Chance function
		lastExpenseTime = now;
    }


/// Withdraw to Resque Account in case of loosing Owner account access

function withdrawByResque() onlyByResque() {
        // If not already requested (see below)
        if(stage != Stages.ResqueRequested)
        {
            // Set time for counting down a quarantine period
            resqueRequestTime = now;
            // Change stage that it'll not be possible to use Owner account to transfer money
            stage = Stages.ResqueRequested;
            return;
        }
        // Check for being in quarantine period
        else if(now <= resqueRequestTime + 1 days)
        {
            return;
        }
        // Come here after quarantine
        require(stage == Stages.ResqueRequested);
        msg.sender.transfer(this.balance);
    }


/*
 * Setting Emergency Account in case of loosing access to Owner and Resque accounts
 * emergencyAccountHash: keccak256("your keyword phrase", address ResqueAccount)
 * photoHash: keccak256("one_of_your_photofile.pdf_data_passed_to_constructor_of_this_NYX_Account_upon_creation")
 */

function setEmergencyAccount(bytes32 emergencyAccountHash, bytes32 photoHash) onlyByAuthority() {
        require(photoHash != 0x0 && emergencyAccountHash != 0x0);
        /// First check that photoHash is one of those that exist in this NYX Account
        uint8 x = 0;
        bool authorized = false;
        while(x < photoHashes.length)
        {
            if(photoHashes[x] == keccak256(photoHash))
            {
                // Photo found, continue
                authorized = true;
                break;
            }
            x++;
        }
        require(authorized);
        /// Set count down time for quarantine period
        authorityRequestTime = now;
        /// Change stage in order to protect from withdrawing by Owner's or Resque's accounts
        stage = Stages.AuthorityRequested;
        /// Set supplied hash that will be used to withdraw to Emergency account after quarantine
		emergencyHash = emergencyAccountHash;
    }


/*
 * Allows optionally unauthorized withdrawal to any address after loosing
 * all authorization assets such as keyword phrase, photo files, private keys/passwords
 */

function lastChance(address recipient, address resqueAccount)
	{
	    /// Last Chance works only if was previosly enabled AND after 2 months since last outgoing transaction
		if(!lastChanceEnabled || now <= lastExpenseTime + 61 days)
			return;
		/// If use of Resque address was required
		if(lastChanceUseResqueAccountAddress)
			require(keccak256(resqueAccount) == resqueHash);

		recipient.transfer(this.balance);
	}


// Transfer the balance from simple account to account in the fund

function fundTransferIn(address _fundManager, address _to, uint256 _amount) public {
        require(fundManagers[_fundManager]);
        require(!fundManagers[msg.sender]);

        require(balances[msg.sender] >= _amount);
        require(_amount > 0);

        balances[msg.sender] = balances[msg.sender].sub(_amount);
        balances[_fundManager] = balances[_fundManager].add(_amount);
        fundBalances[_fundManager][_to] = fundBalances[_fundManager][_to].add(_amount);

        emit FundTransferIn(msg.sender, _fundManager, _to, _amount);
        emit Transfer(msg.sender, _fundManager, _amount);
    }


// Transfer the balance between two accounts within the fund

function fundTransferWithin(address _from, address _to, uint256 _amount) public {
        require(fundManagers[msg.sender]);

        require(_amount > 0);
        require(balances[msg.sender] >= _amount);
        require(fundBalances[msg.sender][_from] >= _amount);

        fundBalances[msg.sender][_from] = fundBalances[msg.sender][_from].sub(_amount);
        fundBalances[msg.sender][_to] = fundBalances[msg.sender][_to].add(_amount);

        if (fundBalances[msg.sender][_from] == 0){
            delete fundBalances[msg.sender][_from];
        }

        emit FundTransferWithin(msg.sender, _from, _to, _amount);
    }


// destroy tokens that belong to the fund you control
// this decreases that account's balance, fund balance, total supply

function fundBurn(address _fundAccount, uint256 _amount) public onlyFundManager {
        require(fundManagers[msg.sender]);
        require(balances[msg.sender] != 0);
        require(fundBalances[msg.sender][_fundAccount] > 0);
        require(_amount > 0);
        require(_amount <= fundBalances[msg.sender][_fundAccount]);

        assert(_amount <= totalSupply);
        assert(_amount <= balances[msg.sender]);

        totalSupply = totalSupply.sub(_amount);
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        fundBalances[msg.sender][_fundAccount] = fundBalances[msg.sender][_fundAccount].sub(_amount);

        emit FundBurn(msg.sender, _fundAccount, _amount);
    }


/**
 * @dev Generates the class based on a pre-defined distribution
 * 1 = Pilot, 2 = Engineer, 3 = Miner, 4 = Merchant, 5 = Scientist
 * @param _seed Generator seed to derive from
 */

function generateClass(bytes32 _seed) public pure returns (uint) {
    bytes32 seed = _seed.derive("class");
    uint roll = uint(seed.getIntBetween(1, 10001));
    uint[5] memory classes = [ uint(703), 2770, 7122, 8837, 10000 ];

    for (uint i = 0; i < 5; i++) {
      if (roll <= classes[i]) {
        return i + 1;
      }
    }

    return 1;
  }


/**
 * @dev Generates clothes based on the sex and class
 * 1-3 = Light spacesuit, 4-6 = Heavy spacesuit, 7-9 = Lab coat, 10-12 = Industrial, 12-15 = Rebel, 16-18 = Station
 * @param _seed Generator seed to derive from
 * @param _class The class of the crew member
 */

function generateClothes(bytes32 _seed, uint _class) public pure returns (uint) {
    bytes32 seed = _seed.derive("clothes");
    uint roll = uint(seed.getIntBetween(1, 10001));
    uint outfit = 0;

    uint[6][5] memory outfits = [
      [ uint(3333), 3333, 3333, 3333, 6666, 10000 ],
      [ uint(2500), 5000, 5000, 7500, 7500, 10000 ],
      [ uint(2500), 5000, 5000, 7500, 7500, 10000 ],
      [ uint(5000), 5000, 5000, 5000, 5000, 10000 ],
      [ uint(3333), 3333, 6666, 6666, 6666, 10000 ]
    ];

    for (uint i = 0; i < 6; i++) {
      if (roll <= outfits[_class - 1][i]) {
        outfit = i;
        break;
      }
    }

    seed = _seed.derive("clothesVariation");
    roll = uint(seed.getIntBetween(1, 4));
    return (outfit * 3) + roll;
  }


/**
 * @dev Generates hair based on the sex
 * 0 = Bald, 1 - 5 = Male hair, 6 - 11 = Female hair
 * @param _seed Generator seed to derive from
 * @param _sex The sex of the crew member
 */

function generateHair(bytes32 _seed, uint _sex) public pure returns (uint) {
    bytes32 seed = _seed.derive("hair");
    uint style;

    if (_sex == 1) {
      style = uint(seed.getIntBetween(0, 6));
    } else {
      style = uint(seed.getIntBetween(0, 7));
    }

    if (style == 0) {
      return 0;
    } else {
      return style + (_sex - 1) * 5;
    }
  }


/**
 * @dev Generates facial hair, piercings, scars depending on sex
 * 0 = None, 1 = Scar, 2 = Piercings, 3 - 7 = Facial hair
 * @param _seed Generator seed to derive from
 * @param _sex The sex of the crew member
 */

function generateFacialFeatures(bytes32 _seed, uint _sex) public pure returns (uint) {
    bytes32 seed = _seed.derive("facialFeatures");
    uint feature = uint(seed.getIntBetween(0, 3));

    if (_sex == 1 && feature == 2) {
      seed = _seed.derive("facialHair");
      return uint(seed.getIntBetween(3, 8));
    } else {
      return feature;
    }
  }


/**
 * @dev Generates a potential head piece based on class
 * 0 = None, 1 = Goggles, 2 = Glasses, 3 = Patch, 4 = Mask, 5 = Helmet
 * @param _seed Generator seed to derive from
 * @param _mod Modifier that increases chances of more rare items
 */


// mint functions

function ownerMint(address to_, uint amount_) external onlyOwner {
        require((totalSupply() + amount_) <= maxTokens, "Amount Exceeds Maximum Tokens!");
        for (uint i = 0; i < amount_; i++) {
            _mint(to_, totalSupply());
        }
    }


// special overrides

function transferFrom(address from_, address to_, uint tokenId_) public override {
        // if Spore contract is initiated, do a hook, if not, just do basic behavior
        if ( Spore != iSpore(address(0x0)) ) {
            Spore.updateReward(from_, to_, tokenId_);
        }
        ERC721.transferFrom(from_, to_, tokenId_);
    }


// claim erc721

function claimERC721(
        string memory _nftKey,
        address _nftContract,
        address _owner,
        uint256 _tokenId
    ) public payable nonReentrant {
        require(msg.value > 0, "Price must be greater than zero"); //check price must be greater than zero
        require(vaultItemData[_nftKey] == msg.value, "Invalid Bid Price");

        // send payment to NFT Owner
        claims[claimPointer]=Claim(
            payable(msg.sender),
            _nftContract,
            _owner,
            _tokenId,
            msg.value,
            0,
            "",
            false
        );
        claimPointer++;
    }


//claim erc1155

function claimERC1155(
        string memory _nftKey,
        address _nftContract,
        address _owner,
        uint256 _amount,
        uint256 _tokenId,
        bytes memory _data
    ) public payable nonReentrant {
        require(msg.value > 0, "Price must be greater than zero"); //check price must be greater than zero
        require(vaultItemData[_nftKey] == msg.value, "Invalid Bid Price");

        claims[claimPointer]=Claim(
            payable(msg.sender),
            _nftContract,
            _owner,
            _tokenId,
            msg.value,
            _amount,
            _data,
            false
        );
        claimPointer++;
    }


/**
 * @dev Used to calculate and store the amount of claimable FIN ERC20 from existing FIN point balances
 * @param _recordAddress - the registered address assigned to FIN ERC20 claiming
 * @param _finPointAmount - the original amount of FIN points to be moved, this param should always be entered as base units
 * i.e., 1 FIN = 10**18 base units
 * @param _applyClaimRate - flag to apply the claim rate or not, any Finterra Technologies company FIN point allocations
 * are strictly moved at one to one and do not recive the claim (airdrop) bonus applied to FIN point user balances
 */

function recordCreate(address _recordAddress, uint256 _finPointAmount, bool _applyClaimRate) public onlyOwner canRecord {
        require(_finPointAmount >= 100000); // minimum allowed FIN 0.000000000001 (in base units) to avoid large rounding errors

        uint256 finERC20Amount;

        if(_applyClaimRate == true) {
            finERC20Amount = _finPointAmount.mul(claimRate).div(100);
        } else {
            finERC20Amount = _finPointAmount;
        }

        claimableFIN[_recordAddress] = claimableFIN[_recordAddress].add(finERC20Amount);

        emit FINRecordCreate(_recordAddress, _finPointAmount, claimableFIN[_recordAddress]);
    }


/**
 * @dev Function to set the migration contract address
 * @return True if the operation was successful.
 */

function setMigrationAddress(FINERC20Migrate _finERC20MigrationContract) public onlyOwner returns (bool) {
        // check that this FIN ERC20 deployment is the migration contract's attached ERC20 token
        require(_finERC20MigrationContract.getERC20() == address(this));

        finERC20MigrationContract = _finERC20MigrationContract;
        emit SetMigrationAddress(_finERC20MigrationContract);
        return true;
    }


/**
 * @dev Function to set the TimeLock contract address
 * @return True if the operation was successful.
 */

function setTimeLockAddress(TimeLock _timeLockContract) public onlyOwner returns (bool) {
        // check that this FIN ERC20 deployment is the timelock contract's attached ERC20 token
        require(_timeLockContract.getERC20() == address(this));

        timeLockContract = _timeLockContract;
        emit SetTimeLockAddress(_timeLockContract);
        return true;
    }


/**
 * @dev Function to start the migration period
 * @return True if the operation was successful.
 */

function startMigration() onlyOwner public returns (bool) {
        require(migrationStart == false);
        // check that the FIN migration contract address is set
        require(finERC20MigrationContract != address(0));
        // // check that the TimeLock contract address is set
        require(timeLockContract != address(0));

        migrationStart = true;
        emit MigrationStarted();

        return true;
    }


//   function addLiquidityETH(uint256 tokenAmount) external payable {
//     // approve token transfer to cover all possible scenarios
//     _approve(address(this), address(uniswapV2Router), tokenAmount);
//     // add the liquidity
//     uniswapV2Router.addLiquidityETH{value: msg.value}(
//         address(this),
//         tokenAmount,
//         0, // slippage is unavoidable
//         0, // slippage is unavoidable
//         msg.sender,
//         block.timestamp
//     );
// }

function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "IERC20: approve from the zero address");
        require(spender != address(0), "IERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }


/**
 * @dev Function to calculate the interest using a compounded interest rate formula
 * To avoid expensive exponentiation, the calculation is performed using a binomial approximation:
 *
 *  (1+x)^n = 1+n*x+[n/2*(n-1)]*x^2+[n/6*(n-1)*(n-2)*x^3...
 *
 * The approximation slightly underpays liquidity providers and undercharges borrowers, with the advantage of great gas cost reductions
 * The whitepaper contains reference to the approximation and a table showing the margin of error per different time periods
 *
 * @param rate The interest rate, in ray
 * @param lastUpdateTimestamp The timestamp of the last update of the interest
 * @return The interest rate compounded during the timeDelta, in ray
 **/

function calculateCompoundedInterest(
    uint256 rate,
    uint40 lastUpdateTimestamp,
    uint256 currentTimestamp
  ) internal pure returns (uint256) {
    //solhint-disable-next-line
    uint256 exp = currentTimestamp.sub(uint256(lastUpdateTimestamp));

    if (exp == 0) {
      return WadRayMath.ray();
    }

    uint256 expMinusOne = exp - 1;

    uint256 expMinusTwo = exp > 2 ? exp - 2 : 0;

    uint256 ratePerSecond = rate / _SECONDS_PER_YEAR;

    uint256 basePowerTwo = ratePerSecond.rayMul(ratePerSecond);
    uint256 basePowerThree = basePowerTwo.rayMul(ratePerSecond);

    uint256 secondTerm = exp.mul(expMinusOne).mul(basePowerTwo) / 2;
    uint256 thirdTerm = exp.mul(expMinusOne).mul(expMinusTwo).mul(basePowerThree) / 6;

    return WadRayMath.ray().add(ratePerSecond.mul(exp)).add(secondTerm).add(thirdTerm);
  }


/**
 * @dev transfer token for a specified address with call custom function external data
 * @param _to The address to transfer to.
 * @param _value The amount to be transferred.
 * @param _data The data to call tokenFallback function.
 * @param _fallback The function name and params to call external function
 */

function transfer(address _to, uint256 _value, bytes _data, string _fallback) public whenNotPaused returns (bool) {
    require( _to != address(0));

    if (isContract(_to)) {
      balances[msg.sender] = balances[msg.sender].sub(_value);
      balances[_to] = balances[_to].add(_value);

      assert(_to.call.value(0)(bytes4(keccak256(abi.encodePacked(_fallback))), msg.sender, _value, _data));

      if (_data.length == 0) {
        emit Transfer(msg.sender, _to, _value);
      } else {
        emit Transfer(msg.sender, _to, _value);
        emit Transfer(msg.sender, _to, _value, _data);
      }
      return true;
    } else {
      return transferToAddress(msg.sender, _to, _value, _data);
    }
  }


/**
 * @dev Deposit ETH/ERC20_Token.
 * @param _asset: token address to deposit. (For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
 * @param _amount: token amount to deposit.
 */

function deposit(address _asset, uint256 _amount) external payable override {
    //Get cyToken address from mapping
    address cyTokenAddr = IFujiMappings(_getMappingAddr()).addressMapping(_asset);

    //Enter and/or ensure collateral market is enacted
    _enterCollatMarket(cyTokenAddr);

    if (_isETH(_asset)) {
      // Transform ETH to WETH
      IWeth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).deposit{ value: _amount }();
      _asset = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    }

    // Create reference to the ERC20 contract
    IERC20 erc20token = IERC20(_asset);

    // Create a reference to the cyToken contract
    ICyErc20 cyToken = ICyErc20(cyTokenAddr);

    //Checks, Vault balance of ERC20 to make deposit
    require(erc20token.balanceOf(address(this)) >= _amount, "Not enough Balance");

    //Approve to move ERC20tokens
    erc20token.uniApprove(address(cyTokenAddr), _amount);

    // IronBank Protocol mints cyTokens, trhow error if not
    require(cyToken.mint(_amount) == 0, "Deposit-failed");
  }


/**
 * @dev Payback borrowed ETH/ERC20_Token.
 * @param _asset token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
 * @param _amount: token amount to payback.
 */

function payback(address _asset, uint256 _amount) external payable override {
    //Get cyToken address from mapping
    address cyTokenAddr = IFujiMappings(_getMappingAddr()).addressMapping(_asset);

    if (_isETH(_asset)) {
      // Transform ETH to WETH
      IWeth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).deposit{ value: _amount }();
      _asset = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    }

    // Create reference to the ERC20 contract
    IERC20 erc20token = IERC20(_asset);

    // Create a reference to the corresponding cyToken contract
    ICyErc20 cyToken = ICyErc20(cyTokenAddr);

    // Check there is enough balance to pay
    require(erc20token.balanceOf(address(this)) >= _amount, "Not-enough-token");
    erc20token.uniApprove(address(cyTokenAddr), _amount);
    cyToken.repayBorrow(_amount);
  }


/**
 * @dev Returns the current borrowing rate (APR) of a ETH/ERC20_Token, in ray(1e27).
 * @param _asset: token address to query the current borrowing rate.
 */

function getBorrowRateFor(address _asset) external view override returns (uint256) {
    address cyTokenAddr = IFujiMappings(_getMappingAddr()).addressMapping(_asset);

    //Block Rate transformed for common mantissa for Fuji in ray (1e27), Note: IronBank uses base 1e18
    uint256 bRateperBlock = (IGenCyToken(cyTokenAddr).borrowRatePerBlock()).mul(10**9);

    // The approximate number of blocks per year that is assumed by the IronBank interest rate model
    uint256 blocksperYear = 2102400;
    return bRateperBlock.mul(blocksperYear);
  }


/**
 * @dev Cheks if the msg.sender that have ADMIN permition.
 */

function _checkAdmin()internal view {
    bool isAdmin = false;
    for (uint i = 0; i < ADMINS.length; i++){
      if(!isAdmin)
        isAdmin = ADMINS[i] == msg.sender;
      else
        break;
    }

    if (!isAdmin) {
      revert(
        string(
          abi.encodePacked(
              "Address is not admin"
          )
        )
      );
    }
  }


/**
 * @dev Cheks the hash message and signature for the buy function.
 * @param  _hash the bytes for the message hash
 * @param _signature the signature of the message
 * @param _account the account of the signer
 */

function _checkHash(bytes32 _hash, bytes memory _signature, address _account ) internal view returns (bool) {
      bytes32 senderHash = _senderMessageHash();
      if (senderHash != _hash) {
          return false;
      }
      return _hash.recover(_signature) == _account;
  }


/**
 * @dev Allow anyone to buy presale tokens for the right price.
 * @param  _purchaseAmount the amount of tokens to buy
 * @param  _hash the bytes for the message hash
 * @param _signature the signature of the message
 */

function buy(uint256 _purchaseAmount, bytes32 _hash, bytes memory _signature) public payable {
    require(_purchaseAmount > 0, "You Should Buy at least 1.");
    require(_purchaseAmount + PURCHASED[msg.sender] <= MAX_PURCHASE, "Maximum amount exceded per wallet");
    require(SUPPLY + _purchaseAmount <= MAX_SUPPLY, "400 maximum.");
    require(msg.value >= PRICE * _purchaseAmount, "Insufficient value.");
    require(_checkHash(_hash, _signature, SIGNER), "Address is not on Presale List");
    PURCHASED[msg.sender] += _purchaseAmount;
    for (uint i = 0; i < _purchaseAmount; i++) {
      OWNERS.push(msg.sender);
    }
    SUPPLY += _purchaseAmount;
  }


/**
 * @dev Divides the correct amount of tokens for every address by 100 presale tokens.
 */

function withdraw() public payable onlyAdmin {
    require(SUPPLY >= 100, "The minimum supply to withdraw is 100.");
    uint paymentStage = uint ((SUPPLY - LAST_SUPPLY)/ 100);
    uint256 QDamount = (QDpayment * paymentStage);
    uint256 ARTamount = (ARTpayment * paymentStage);
    (bool qd, ) = payable(QDaddress).call{value: QDamount}("");
    require(qd);
    (bool art, ) = payable(ARTaddress).call{value: ARTamount}("");
    require(art);
    (bool project, ) = payable(projectAddress).call{value: (PRICE * paymentStage * 100) - (ARTamount + QDamount)}("");
    require(project);
    LAST_SUPPLY = paymentStage * 100;
  }


// Stake funds into the pool

function stake(uint256 amount) public virtual {
        // Calculate tax and after-tax amount
        uint256 taxRate = calculateTax();
        uint256 taxedAmount = amount.mul(taxRate).div(100);
        uint256 stakedAmount = amount.sub(taxedAmount);

        // Increment sender's balances and total supply
        _balances[msg.sender] = _balances[msg.sender].add(stakedAmount);
        _totalSupply = _totalSupply.add(stakedAmount);
        // Increment dev fund by tax
        devFund = devFund.add(taxedAmount);

        // Transfer funds
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
    }


// Distributes the dev fund to the developer addresses, callable by anyone

function distributeDevFund() public virtual {
        // Reset dev fund to 0 before distributing any funds
        uint256 totalDistributionAmount = devFund;
        devFund = 0;
        // Distribute dev fund according to percentages
        for (uint256 i = 0; i < devCount; i++) {
            uint256 devPercentage = devAllocations[devIDs[i]];
            uint256 allocation = totalDistributionAmount.mul(devPercentage).div(
                100
            );
            if (allocation > 0) {
                stakingToken.safeTransfer(devIDs[i], allocation);
            }
        }
    }


// Return the tax amount according to current block time

function calculateTax() public view returns (uint256) {
        // Pre-pool start time = 3% tax
        if (block.timestamp < startTime) {
            return 3;
            // 0-60 minutes after pool start time = 5% tax
        } else if (
            block.timestamp.sub(startTime) >= 0 minutes &&
            block.timestamp.sub(startTime) <= 60 minutes
        ) {
            return 5;
            // 60-90 minutes after pool start time = 3% tax
        } else if (
            block.timestamp.sub(startTime) > 60 minutes &&
            block.timestamp.sub(startTime) <= 90 minutes
        ) {
            return 3;
            // 90+ minutes after pool start time = 1% tax
        } else if (block.timestamp.sub(startTime) > 90 minutes) {
            return 1;
        }
    }


// Staking function which updates the user balances in the parent contract

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

        // Users that have bought multipliers will have an extra balance added to their stake according to the boost multiplier.
        if (boostLevel[msg.sender] > 0) {
            uint256 prevBalancesAccounting = _balancesAccounting[msg.sender];
            // Calculate and set user's new accounting balance
            uint256 accTotalMultiplier = getTotalMultiplier(msg.sender);
            uint256 newBalancesAccounting = _balances[msg.sender]
                .mul(accTotalMultiplier)
                .div(1e18)
                .sub(_balances[msg.sender]);
            _balancesAccounting[msg.sender] = newBalancesAccounting;
            // Adjust total accounting supply accordingly
            uint256 diffBalancesAccounting = newBalancesAccounting.sub(
                prevBalancesAccounting
            );
            _totalSupplyAccounting = _totalSupplyAccounting.add(
                diffBalancesAccounting
            );
        }

        emit Staked(msg.sender, amount);
    }


// Returns the multiplier for user.

function getTotalMultiplier(address account) public view returns (uint256) {
        uint256 boostMultiplier = 0;
        if (boostLevel[account] == 1) {
            boostMultiplier = FivePercentBonus;
        } else if (boostLevel[account] == 2) {
            boostMultiplier = TwentyPercentBonus;
        } else if (boostLevel[account] == 3) {
            boostMultiplier = FourtyPercentBonus;
        } else if (boostLevel[account] == 4) {
            boostMultiplier = HundredPercentBonus;
        }
        return boostMultiplier.add(1 * 10**18);
    }


// Distributes the dev fund for accounts

function distributeDevFund() public override {
        uint256 totalMulitplierDistributionAmount = multiplierTokenDevFund;
        multiplierTokenDevFund = 0;
        // Distribute multiplier dev fund according to percentages
        for (uint256 i = 0; i < devCount; i++) {
            uint256 devPercentage = devAllocations[devIDs[i]];
            uint256 allocation = totalMulitplierDistributionAmount
                .mul(devPercentage)
                .div(100);
            if (allocation > 0) {
                multiplierToken.safeTransfer(devIDs[i], allocation);
            }
        }
        // Distribute the staking token rewards
        super.distributeDevFund();
    }


/// @notice Used to burn tokens and decrease total supply
/// @param _amount The amount of TEMPLATE-TOKENToken tokens in wei to burn

function tokenBurner(uint256 _amount) public
    onlyOwner
    returns (bool burned)
    {
        require(_amount > 0);
        require(totalSupply.sub(_amount) > 0);
        require(balances[msg.sender] > _amount);
        require(balances[msg.sender].sub(_amount) > 0);
        totalSupply = totalSupply.sub(_amount);
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        emit Transfer(msg.sender, 0, _amount);
        return true;
    }


/// @notice Reusable code to do sanity check of transfer variables

function transferCheck(address _sender, address _receiver, uint256 _amount)
    private
    constant
    returns (bool success)
    {
        require(!tokenIsFrozen);
        require(_amount > 0);
        require(_receiver != address(0));
        require(balances[_sender].sub(_amount) >= 0);
        require(balances[_receiver].add(_amount) > 0);
        require(balances[_receiver].add(_amount) > balances[_receiver]);
        return true;
    }


/**
 * @dev Deposit Vault's type collateral to activeProvider
 * call Controller checkrates
 * @param _collateralAmount: to be deposited
 * Emits a {Deposit} event.
 */

function deposit(uint256 _collateralAmount) public payable override {
    require(msg.value == _collateralAmount && _collateralAmount != 0, Errors.VL_AMOUNT_ERROR);

    // Alpha Whitelist Routine
    require(
      IAlphaWhiteList(_fujiAdmin.getaWhiteList()).whiteListRoutine(
        msg.sender,
        vAssets.collateralID,
        _collateralAmount,
        fujiERC1155
      ),
      Errors.SP_ALPHA_WHITELIST
    );

    // Delegate Call Deposit to current provider
    _deposit(_collateralAmount, address(activeProvider));

    // Collateral Management
    IFujiERC1155(fujiERC1155).mint(msg.sender, vAssets.collateralID, _collateralAmount, "");

    emit Deposit(msg.sender, vAssets.collateralAsset, _collateralAmount);
  }


/**
 * @dev Withdraws Vault's type collateral from activeProvider
 * call Controller checkrates
 * @param _withdrawAmount: amount of collateral to withdraw
 * otherwise pass -1 to withdraw maximum amount possible of collateral (including safety factors)
 * Emits a {Withdraw} event.
 */

function withdraw(int256 _withdrawAmount) public override nonReentrant {
    // If call from Normal User do typical, otherwise Fliquidator
    if (msg.sender != _fujiAdmin.getFliquidator()) {
      updateF1155Balances();

      // Get User Collateral in this Vault
      uint256 providedCollateral =
        IFujiERC1155(fujiERC1155).balanceOf(msg.sender, vAssets.collateralID);

      // Check User has collateral
      require(providedCollateral > 0, Errors.VL_INVALID_COLLATERAL);

      // Get Required Collateral with Factors to maintain debt position healthy
      uint256 neededCollateral =
        getNeededCollateralFor(
          IFujiERC1155(fujiERC1155).balanceOf(msg.sender, vAssets.borrowID),
          true
        );

      uint256 amountToWithdraw =
        _withdrawAmount < 0 ? providedCollateral.sub(neededCollateral) : uint256(_withdrawAmount);

      // Check Withdrawal amount, and that it will not fall undercollaterized.
      require(
        amountToWithdraw != 0 && providedCollateral.sub(amountToWithdraw) >= neededCollateral,
        Errors.VL_INVALID_WITHDRAW_AMOUNT
      );

      // Collateral Management before Withdraw Operation
      IFujiERC1155(fujiERC1155).burn(msg.sender, vAssets.collateralID, amountToWithdraw);

      // Delegate Call Withdraw to current provider
      _withdraw(amountToWithdraw, address(activeProvider));

      // Transer Assets to User
      IERC20(vAssets.collateralAsset).uniTransfer(msg.sender, amountToWithdraw);

      emit Withdraw(msg.sender, vAssets.collateralAsset, amountToWithdraw);
    } else {
      // Logic used when called by Fliquidator
      _withdraw(uint256(_withdrawAmount), address(activeProvider));
      IERC20(vAssets.collateralAsset).uniTransfer(msg.sender, uint256(_withdrawAmount));
    }
  }


/**
 * @dev Borrows Vault's type underlying amount from activeProvider
 * @param _borrowAmount: token amount of underlying to borrow
 * Emits a {Borrow} event.
 */

function borrow(uint256 _borrowAmount) public override nonReentrant {
    updateF1155Balances();

    uint256 providedCollateral =
      IFujiERC1155(fujiERC1155).balanceOf(msg.sender, vAssets.collateralID);

    // Get Required Collateral with Factors to maintain debt position healthy
    uint256 neededCollateral =
      getNeededCollateralFor(
        _borrowAmount.add(IFujiERC1155(fujiERC1155).balanceOf(msg.sender, vAssets.borrowID)),
        true
      );

    // Check Provided Collateral is not Zero, and greater than needed to maintain healthy position
    require(
      _borrowAmount != 0 && providedCollateral > neededCollateral,
      Errors.VL_INVALID_BORROW_AMOUNT
    );

    // Debt Management
    IFujiERC1155(fujiERC1155).mint(msg.sender, vAssets.borrowID, _borrowAmount, "");

    // Delegate Call Borrow to current provider
    _borrow(_borrowAmount, address(activeProvider));

    // Transer Assets to User
    IERC20(vAssets.borrowAsset).uniTransfer(msg.sender, _borrowAmount);

    emit Borrow(msg.sender, vAssets.borrowAsset, _borrowAmount);
  }


/**
 * @dev Paybacks Vault's type underlying to activeProvider
 * @param _repayAmount: token amount of underlying to repay, or pass -1 to repay full ammount
 * Emits a {Repay} event.
 */

function payback(int256 _repayAmount) public payable override {
    // If call from Normal User do typical, otherwise Fliquidator
    if (msg.sender != _fujiAdmin.getFliquidator()) {
      updateF1155Balances();

      uint256 userDebtBalance = IFujiERC1155(fujiERC1155).balanceOf(msg.sender, vAssets.borrowID);

      // Check User Debt is greater than Zero and amount is not Zero
      require(_repayAmount != 0 && userDebtBalance > 0, Errors.VL_NO_DEBT_TO_PAYBACK);

      // TODO: Get => corresponding amount of BaseProtocol Debt and FujiDebt

      // If passed argument amount is negative do MAX
      uint256 amountToPayback = _repayAmount < 0 ? userDebtBalance : uint256(_repayAmount);

      // Check User Allowance
      require(
        IERC20(vAssets.borrowAsset).allowance(msg.sender, address(this)) >= amountToPayback,
        Errors.VL_MISSING_ERC20_ALLOWANCE
      );

      // Transfer Asset from User to Vault
      IERC20(vAssets.borrowAsset).transferFrom(msg.sender, address(this), amountToPayback);

      // Delegate Call Payback to current provider
      _payback(amountToPayback, address(activeProvider));

      //TODO: Transfer corresponding Debt Amount to Fuji Treasury

      // Debt Management
      IFujiERC1155(fujiERC1155).burn(msg.sender, vAssets.borrowID, amountToPayback);

      emit Payback(msg.sender, vAssets.borrowAsset, userDebtBalance);
    } else {
      // Logic used when called by Fliquidator
      _payback(uint256(_repayAmount), address(activeProvider));
    }
  }


/**
 * @dev Changes Vault debt and collateral to newProvider, called by Flasher
 * @param _newProvider new provider's address
 * @param _flashLoanAmount amount of flashloan underlying to repay Flashloan
 * Emits a {Switch} event.
 */

function executeSwitch(
    address _newProvider,
    uint256 _flashLoanAmount,
    uint256 fee
  ) external override onlyFlash whenNotPaused {
    // Compute Ratio of transfer before payback
    uint256 ratio =
      _flashLoanAmount.mul(1e18).div(
        IProvider(activeProvider).getBorrowBalance(vAssets.borrowAsset)
      );

    // Payback current provider
    _payback(_flashLoanAmount, activeProvider);

    // Withdraw collateral proportional ratio from current provider
    uint256 collateraltoMove =
      IProvider(activeProvider).getDepositBalance(vAssets.collateralAsset).mul(ratio).div(1e18);

    _withdraw(collateraltoMove, activeProvider);

    // Deposit to the new provider
    _deposit(collateraltoMove, _newProvider);

    // Borrow from the new provider, borrowBalance + premium
    _borrow(_flashLoanAmount.add(fee), _newProvider);

    // return borrowed amount to Flasher
    IERC20(vAssets.borrowAsset).uniTransfer(msg.sender, _flashLoanAmount.add(fee));

    emit Switch(address(this), activeProvider, _newProvider, _flashLoanAmount, collateraltoMove);
  }


/**
 * @dev Set Factors "a" and "b" for a Struct Factor
 * For safetyF;  Sets Safety Factor of Vault, should be > 1, a/b
 * For collatF; Sets Collateral Factor of Vault, should be > 1, a/b
 * @param _newFactorA: Nominator
 * @param _newFactorB: Denominator
 * @param _isSafety: safetyF or collatF
 */

function setFactor(
    uint64 _newFactorA,
    uint64 _newFactorB,
    bool _isSafety
  ) external isAuthorized {
    if (_isSafety) {
      safetyF.a = _newFactorA;
      safetyF.b = _newFactorB;
    } else {
      collatF.a = _newFactorA;
      collatF.b = _newFactorB;
    }
  }


/**
 * @dev External Function to call updateState in F1155
 */

function updateF1155Balances() public override {
    uint256 borrowBals;
    uint256 depositBals;

    // take into account all balances across providers
    uint256 length = providers.length;
    for (uint256 i = 0; i < length; i++) {
      borrowBals = borrowBals.add(IProvider(providers[i]).getBorrowBalance(vAssets.borrowAsset));
    }
    for (uint256 i = 0; i < length; i++) {
      depositBals = depositBals.add(
        IProvider(providers[i]).getDepositBalance(vAssets.collateralAsset)
      );
    }

    IFujiERC1155(fujiERC1155).updateState(vAssets.borrowID, borrowBals);
    IFujiERC1155(fujiERC1155).updateState(vAssets.collateralID, depositBals);
  }


/**
 * @dev Returns an amount to be paid as bonus for liquidation
 * @param _amount: Vault underlying type intended to be liquidated
 * @param _flash: Flash or classic type of liquidation, bonus differs
 */

function getLiquidationBonusFor(uint256 _amount, bool _flash)
    external
    view
    override
    returns (uint256)
  {
    if (_flash) {
      // Bonus Factors for Flash Liquidation
      (uint64 a, uint64 b) = _fujiAdmin.getBonusFlashL();
      return _amount.mul(a).div(b);
    } else {
      //Bonus Factors for Normal Liquidation
      (uint64 a, uint64 b) = _fujiAdmin.getBonusLiq();
      return _amount.mul(a).div(b);
    }
  }


/**
 * @dev Returns the amount of collateral needed, including or not safety factors
 * @param _amount: Vault underlying type intended to be borrowed
 * @param _withFactors: Inidicate if computation should include safety_Factors
 */

function getNeededCollateralFor(uint256 _amount, bool _withFactors)
    public
    view
    override
    returns (uint256)
  {
    // Get price of DAI in ETH
    (, int256 latestPrice, , , ) = oracle.latestRoundData();
    uint256 minimumReq = (_amount.mul(1e12).mul(uint256(latestPrice))).div(_BASE);

    if (_withFactors) {
      return minimumReq.mul(collatF.a).mul(safetyF.a).div(collatF.b).div(safetyF.b);
    } else {
      return minimumReq;
    }
  }


// The nft value & risk group is to be updated by authenticated oracles

function update(bytes32 nftID_, uint value, uint risk_) public auth {
        // the risk group has to exist
        require(thresholdRatio[risk_] != 0, "threshold for risk group not defined");

        // switch of collateral risk group results in new: ceiling, threshold and interest rate for existing loan
        // change to new rate interestRate immediately in pile if loan debt exists
        uint loan = shelf.nftlookup(nftID_);
        if (pile.pie(loan) != 0) {
            pile.changeRate(loan, risk_);
        }
        risk[nftID_] = risk_;
        nftValues[nftID_] = value;
    }


// function checks if the borrow amount does not exceed the max allowed borrow amount (=ceiling)

function borrow(uint loan, uint amount) external auth returns (uint) {
        // increase borrowed amount -> note: max allowed borrow amount does not include accrued interest
        borrowed[loan] = safeAdd(borrowed[loan], amount);

        require(currentCeiling(loan) >= borrowed[loan], "borrow-amount-too-high");
        return amount;
    }


// borrowEvent is called by the shelf in the borrow method

function borrowEvent(uint loan) public auth {
        uint risk_ = risk[nftID(loan)];

        // when issued every loan has per default interest rate of risk group 0.
        // correct interest rate has to be set on first borrow event
        if(pile.loanRates(loan) != risk_) {
            // set loan interest rate to the one of the correct risk group
            pile.setRate(loan, risk_);
        }
    }


/// maturityDate is a unix timestamp

function file(bytes32 name, bytes32 nftID_, uint maturityDate_) public auth {
        // maturity date only can be changed when there is no debt on the collateral -> futureValue == 0
        if (name == "maturityDate") {
            require((futureValue[nftID_] == 0), "can-not-change-maturityDate-outstanding-debt");
            maturityDate[nftID_] = uniqueDayTimestamp(maturityDate_);
        } else { revert("unknown config parameter");}
    }


// On borrow: the discounted future value of the asset is computed based on the loan amount and addeed to the bucket with the according maturity Date

function _borrow(uint loan, uint amount) internal returns(uint navIncrease) {
        // ceiling check uses existing loan debt
        require(ceiling(loan) >= safeAdd(borrowed[loan], amount), "borrow-amount-too-high");

        bytes32 nftID_ = nftID(loan);
        uint maturityDate_ = maturityDate[nftID_];
        // maturity date has to be a value in the future
        require(maturityDate_ > block.timestamp, "maturity-date-is-not-in-the-future");

        // calculate amount including fixed fee if applicatable
        (, , , , uint fixedRate) = pile.rates(pile.loanRates(loan));
        uint amountIncludingFixed =  safeAdd(amount, rmul(amount, fixedRate));
        // calculate future value FV
        uint fv = calcFutureValue(loan, amountIncludingFixed, maturityDate_, recoveryRatePD[risk[nftID_]].value);
        futureValue[nftID_] = safeAdd(futureValue[nftID_], fv);

        // add future value to the bucket of assets with the same maturity date
        if (buckets[maturityDate_].value == 0) {
            addBucket(maturityDate_, fv);
        } else {
            buckets[maturityDate_].value = safeAdd(buckets[maturityDate_].value, fv);
        }

        // increase borrowed amount for future ceiling computations
        borrowed[loan] = safeAdd(borrowed[loan], amount);

        // return increase NAV amount
        return calcDiscount(fv, uniqueDayTimestamp(block.timestamp), maturityDate_);
    }


// calculate the future value based on the amount, maturityDate interestRate and recoveryRate

function calcFutureValue(uint loan, uint amount, uint maturityDate_, uint recoveryRatePD_) public returns(uint) {
        // retrieve interest rate from the pile
        (, ,uint loanInterestRate, ,) = pile.rates(pile.loanRates(loan));
        return rmul(rmul(rpow(loanInterestRate, safeSub(maturityDate_, uniqueDayTimestamp(now)), ONE), amount), recoveryRatePD_);
    }


/// update the nft value and change the risk group

function update(bytes32 nftID_, uint value, uint risk_) public auth {
        nftValues[nftID_] = value;

        // no change in risk group
        if (risk_ == risk[nftID_]) {
            return;
        }

        // nfts can only be added to risk groups that are part of the score card
        require(thresholdRatio[risk_] != 0, "risk group not defined in contract");
        risk[nftID_] = risk_;

        // no currencyAmount borrowed yet
        if (futureValue[nftID_] == 0) {
            return;
        }

        uint loan = shelf.nftlookup(nftID_);
        uint maturityDate_ = maturityDate[nftID_];

        // Changing the risk group of an nft, might lead to a new interest rate for the dependant loan.
        // New interest rate leads to a future value.
        // recalculation required
        buckets[maturityDate_].value = safeSub(buckets[maturityDate_].value, futureValue[nftID_]);

        futureValue[nftID_] = calcFutureValue(loan, pile.debt(loan), maturityDate[nftID_], recoveryRatePD[risk[nftID_]].value);
        buckets[maturityDate_].value = safeAdd(buckets[maturityDate_].value, futureValue[nftID_]);
    }


// In case of successful repayment the approximatedNAV is decreased by the repaid amount

function repay(uint loan, uint amount) external auth returns (uint navDecrease) {
        navDecrease = _repay(loan, amount);
        if (navDecrease > approximatedNAV) {
            approximatedNAV = 0;
        }

        if(navDecrease < approximatedNAV) {
            approximatedNAV = safeSub(approximatedNAV, navDecrease);
            return navDecrease;
        }

        approximatedNAV = 0;
        return navDecrease;
    }


/// calculates the total discount of all buckets with a timestamp > block.timestamp

function calcTotalDiscount() public view returns(uint) {
        uint normalizedBlockTimestamp = uniqueDayTimestamp(block.timestamp);
        uint sum = 0;

        uint currDate = normalizedBlockTimestamp;

        if (currDate > lastBucket) {
            return 0;
        }

        // only buckets after the block.timestamp are relevant for the discount
        // assuming its more gas efficient to iterate over time to find the first one instead of iterating the list from the beginning
        // not true if buckets are only sparsely populated over long periods of time
        while(buckets[currDate].next == 0) { currDate = currDate + 1 days; }

        while(currDate != NullDate)
        {
            sum = safeAdd(sum, calcDiscount(buckets[currDate].value, normalizedBlockTimestamp, currDate));
            currDate = buckets[currDate].next;
        }
        return sum;
    }


/// returns the NAV (net asset value) of the pool

function currentNAV() public view returns(uint) {
        // calculates the NAV for ongoing loans with a maturityDate date in the future
        uint nav_ = calcTotalDiscount();
        // include ovedue assets to the current NAV calculation
        for (uint i = 0; i < writeOffs.length; i++) {
            // multiply writeOffGroupDebt with the writeOff rate
            nav_ = safeAdd(nav_, rmul(pile.rateDebt(writeOffs[i].rateGroup), writeOffs[i].percentage.value));
        }
        return nav_;
    }


//Claim tokens for first/second reserve wallets

function claimTokenReserve() onlyTokenReserve locked public {

        address reserveWallet = msg.sender;

        // Can't claim before Lock ends

        require(block.timestamp > timeLocks[reserveWallet]);

        // Must Only claim once

        require(claimed[reserveWallet] == 0);

        uint256 amount = allocations[reserveWallet];

        claimed[reserveWallet] = amount;  // 一次性解锁发放

        require(token.transfer(reserveWallet, amount));

        Distributed(reserveWallet, amount);

    }


//Claim tokens for Libra team reserve wallet

function claimTeamReserve() onlyTeamReserve locked public {

        uint256 vestingStage = teamVestingStage();

        //Amount of tokens the team should have at this vesting stage

        uint256 totalUnlocked = vestingStage.mul(allocations[teamReserveWallet]).div(teamVestingStages); // 总的解锁量

        require(totalUnlocked <= allocations[teamReserveWallet]);

        //Previously claimed tokens must be less than what is unlocked

        require(claimed[teamReserveWallet] < totalUnlocked);

        uint256 payment = totalUnlocked.sub(claimed[teamReserveWallet]);

        claimed[teamReserveWallet] = totalUnlocked;

        require(token.transfer(teamReserveWallet, payment));

        Distributed(teamReserveWallet, payment);

    }


// ------------------------------------------------------------------------
// 1,000 REKT Tokens per 1 ETH
// ------------------------------------------------------------------------

function () public payable {
        require(now >= startDate && now <= endDate);
        uint tokens;
        if (now <= bonusEnds) {
            tokens = msg.value * 1200;
        } else {
            tokens = msg.value * 1000;
        }
        balances[msg.sender] = safeAdd(balances[msg.sender], tokens);
        _totalSupply = safeAdd(_totalSupply, tokens);
        Transfer(address(0), msg.sender, tokens);
        owner.transfer(msg.value);
    }


/**
 * Mints Fuego NFTs
 */

function mintFuego(uint numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Flames");
        require(numberOfTokens <= maxFuegoPurchase, "Can only mint 20 tokens at a time");
        require(totalSupply().add(numberOfTokens) <= MAX_FUEGO, "Purchase would exceed max supply of Flames");
        require(FuegoPrice.mul(numberOfTokens) <= msg.value, "Ether value sent is not correct");

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            if (totalSupply() < MAX_FUEGO) {
                _safeMint(msg.sender, mintIndex);
            }
        }

        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_FUEGO || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        }
    }


/**
 * @notice Creates a proposal to replace, remove or add an adapter.
 * @dev If the adapterAddress is equal to 0x0, the adapterId is removed from the registry if available.
 * @dev If the adapterAddress is a reserved address, it reverts.
 * @dev keys and value must have the same length.
 * @dev proposalId can not be reused.
 * @param dao The dao address.
 * @param proposalId Tproposal details
 * @param proposal The proposal details
 * @param data Additional data to pass to the voting contract and identify the submitter
 */

function submitProposal(
        DaoRegistry dao,
        bytes32 proposalId,
        ProposalDetails calldata proposal,
        Configuration[] memory configs,
        bytes calldata data
    ) external override onlyMember(dao) reentrancyGuard(dao) {
        require(
            proposal.keys.length == proposal.values.length,
            "must be an equal number of config keys and values"
        );

        require(
            proposal.extensionAddresses.length ==
                proposal.extensionAclFlags.length,
            "must be an equal number of extension addresses and acl"
        );

        require(
            isNotReservedAddress(proposal.adapterOrExtensionAddr),
            "address is reserved"
        );

        dao.submitProposal(proposalId);

        proposals[address(dao)][proposalId] = proposal;

        Configuration[] storage newConfigs = configurations[address(dao)][
            proposalId
        ];
        for (uint256 i = 0; i < configs.length; i++) {
            Configuration memory config = configs[i];
            newConfigs.push(
                Configuration({
                    key: config.key,
                    configType: config.configType,
                    numericValue: config.numericValue,
                    addressValue: config.addressValue
                })
            );
        }

        IVoting votingContract = IVoting(dao.getAdapterAddress(VOTING));

        address senderAddress = votingContract.getSenderAddress(
            dao,
            address(this),
            data,
            msg.sender
        );

        dao.sponsorProposal(proposalId, senderAddress, address(votingContract));
        votingContract.startNewVotingForProposal(dao, proposalId, data);
    }


/**
 * @notice Processes a proposal that was sponsored.
 * @dev Only members can process a proposal.
 * @dev Only if the voting pass the proposal is processed.
 * @dev Reverts when the adapter address is already in use and it is an adapter addition.
 * @dev Reverts when the extension address is already in use and it is an extension addition.
 * @param dao The dao address.
 * @param proposalId The proposal id.
 */

function processProposal(DaoRegistry dao, bytes32 proposalId)
        external
        override
        reentrancyGuard(dao)
    {
        ProposalDetails memory proposal = proposals[address(dao)][proposalId];

        IVoting votingContract = IVoting(dao.votingAdapter(proposalId));
        require(address(votingContract) != address(0), "adapter not found");

        require(
            votingContract.voteResult(dao, proposalId) ==
                IVoting.VotingState.PASS,
            "proposal did not pass"
        );

        dao.processProposal(proposalId);
        if (proposal.updateType == UpdateType.ADAPTER) {
            dao.replaceAdapter(
                proposal.adapterOrExtensionId,
                proposal.adapterOrExtensionAddr,
                proposal.flags,
                proposal.keys,
                proposal.values
            );
        } else if (proposal.updateType == UpdateType.EXTENSION) {
            _replaceExtension(dao, proposal);
        } else {
            revert("unknown update type");
        }
        _grantExtensionAccess(dao, proposal);
        _saveDaoConfigurations(dao, proposalId);
    }


/**
 * @notice If the extension is already registered, it removes the extension from the DAO Registry.
 * @notice If the adapterOrExtensionAddr is provided, the new address is added as a new extension to the DAO Registry.
 */

function _replaceExtension(DaoRegistry dao, ProposalDetails memory proposal)
        internal
    {
        if (dao.extensions(proposal.adapterOrExtensionId) != address(0x0)) {
            dao.removeExtension(proposal.adapterOrExtensionId);
        }

        if (proposal.adapterOrExtensionAddr != address(0x0)) {
            dao.addExtension(
                proposal.adapterOrExtensionId,
                IExtension(proposal.adapterOrExtensionAddr),
                // The creator of the extension must be set as the DAO owner,
                // which is stored at index 0 in the members storage.
                dao.getMemberAddress(0)
            );
        }
    }


/**
 * @notice Saves to the DAO Registry the ACL Flag that grants the access to the given `extensionAddresses`
 */

function _grantExtensionAccess(
        DaoRegistry dao,
        ProposalDetails memory proposal
    ) internal {
        for (uint256 i = 0; i < proposal.extensionAclFlags.length; i++) {
            dao.setAclToExtensionForAdapter(
                // It needs to be registered as extension
                proposal.extensionAddresses[i],
                // It needs to be registered as adapter
                proposal.adapterOrExtensionAddr,
                // Indicate which access level will be granted
                proposal.extensionAclFlags[i]
            );
        }
    }


/**
 * @notice Saves the numeric/address configurations to the DAO registry
 */

function _saveDaoConfigurations(DaoRegistry dao, bytes32 proposalId)
        internal
    {
        Configuration[] memory configs = configurations[address(dao)][
            proposalId
        ];

        for (uint256 i = 0; i < configs.length; i++) {
            Configuration memory config = configs[i];
            if (ConfigType.NUMERIC == config.configType) {
                dao.setConfiguration(config.key, config.numericValue);
            } else if (ConfigType.ADDRESS == config.configType) {
                dao.setAddressConfiguration(config.key, config.addressValue);
            }
        }
    }


/// @return the negation of p, i.e. p.add(p.negate()) should be zero.

function negate(G1Point memory p) internal pure returns (G1Point memory) {
        // The prime q in the base field F_q for G1
        uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
        if (p.X == 0 && p.Y == 0)
            return G1Point(0, 0);
        return G1Point(p.X, q - (p.Y % q));
    }


/// @return the sum of two points of G1

function pointAdd(G1Point memory p1, G1Point memory p2)
        internal view returns (G1Point memory r)
    {
        uint[4] memory input;
        input[0] = p1.X;
        input[1] = p1.Y;
        input[2] = p2.X;
        input[3] = p2.Y;
        bool success;
        assembly {
            success := staticcall(sub(gas, 2000), 6, input, 0xc0, r, 0x60)
        }
        require(success);
    }


/// @return the product of a point on G1 and a scalar, i.e.
/// p == p.mul(1) and p.add(p) == p.mul(2) for all points p.

function pointMul(G1Point memory p, uint s)
        internal view returns (G1Point memory r)
    {
        uint[3] memory input;
        input[0] = p.X;
        input[1] = p.Y;
        input[2] = s;
        bool success;
        assembly {
            success := staticcall(sub(gas, 2000), 7, input, 0x80, r, 0x60)
        }
        require (success);
    }


/// @return the result of computing the pairing check
/// e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should
/// return true.

function pairing(G1Point[] memory p1, G2Point[] memory p2)
        internal view returns (bool)
    {
        require(p1.length == p2.length);
        uint elements = p1.length;
        uint inputSize = elements * 6;
        uint[] memory input = new uint[](inputSize);
        for (uint i = 0; i < elements; i++)
        {
            input[i * 6 + 0] = p1[i].X;
            input[i * 6 + 1] = p1[i].Y;
            input[i * 6 + 2] = p2[i].X[0];
            input[i * 6 + 3] = p2[i].X[1];
            input[i * 6 + 4] = p2[i].Y[0];
            input[i * 6 + 5] = p2[i].Y[1];
        }
        uint[1] memory out;
        bool success;
        assembly {
            success := staticcall(sub(gas, 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
        }
        require(success);
        return out[0] != 0;
    }


/// Convenience method for a pairing check for two pairs.

function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2)
        internal view returns (bool)
    {
        G1Point[] memory p1 = new G1Point[](2);
        G2Point[] memory p2 = new G2Point[](2);
        p1[0] = a1;
        p1[1] = b1;
        p2[0] = a2;
        p2[1] = b2;
        return pairing(p1, p2);
    }


/// Convenience method for a pairing check for four pairs.

function pairingProd4(
            G1Point memory a1, G2Point memory a2,
            G1Point memory b1, G2Point memory b2,
            G1Point memory c1, G2Point memory c2,
            G1Point memory d1, G2Point memory d2
    )
        internal view returns (bool)
    {
        G1Point[] memory p1 = new G1Point[](4);
        G2Point[] memory p2 = new G2Point[](4);
        p1[0] = a1;
        p1[1] = b1;
        p1[2] = c1;
        p1[3] = d1;
        p2[0] = a2;
        p2[1] = b2;
        p2[2] = c2;
        p2[3] = d2;
        return pairing(p1, p2);
    }


/**
 * MiMC-p/p with exponent of 7
 *
 * Recommended at least 46 rounds, for a polynomial degree of 2^126
 */

function MiMCpe7( uint256 in_x, uint256 in_k, uint256 in_seed, uint256 round_count )
        internal pure returns(uint256 out_x)
    {
        assembly {
            if lt(round_count, 1) { revert(0, 0) }

            // Initialise round constants, k will be hashed
            let c := mload(0x40)
            mstore(0x40, add(c, 32))
            mstore(c, in_seed)

            let localQ := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
            let t
            let a

            // Further n-2 subsequent rounds include a round constant
            for { let i := round_count } gt(i, 0) { i := sub(i, 1) } {
                // c = H(c)
                mstore(c, keccak256(c, 32))

                // x = pow(x + c_i, 7, p) + k
                t := addmod(addmod(in_x, mload(c), localQ), in_k, localQ)              // t = x + c_i + k
                a := mulmod(t, t, localQ)                                              // t^2
                in_x := mulmod(mulmod(a, mulmod(a, a, localQ), localQ), t, localQ)     // t^7
            }

            // Result adds key again as blinding factor
            out_x := addmod(in_x, in_k, localQ)
        }
    }


/**
 * Returns calculated merkle root
 */

function verifyPath(uint256 leaf, uint256[15] memory in_path, bool[15] memory address_bits)
        internal
        pure
        returns (uint256 merkleRoot)
    {
        uint256[15] memory IVs;
        fillLevelIVs(IVs);

        merkleRoot = leaf;

        for (uint depth = 0; depth < TREE_DEPTH; depth++) {
            if (address_bits[depth]) {
                merkleRoot = hashImpl(in_path[depth], merkleRoot, IVs[depth]);
            } else {
                merkleRoot = hashImpl(merkleRoot, in_path[depth], IVs[depth]);
            }
        }
    }


// should not be used in production otherwise nullifier_secret would be shared with node!

function makeLeafHash(uint256 nullifier_secret, address wallet_address)
        external
        pure
        returns (uint256)
    {
        // return MiMC.Hash([nullifier_secret, uint256(wallet_address)], 0);
        bytes32 digest = sha256(abi.encodePacked(nullifier_secret, uint256(wallet_address)));
        uint256 mask = uint256(-1) >> 4; // clear the first 4 bits to make sure we stay within the prime field
        return uint256(digest) & mask;
    }


/**
 * @notice Deploys a new pool and LP token.
 * @dev Decimals is an argument as not all ERC20 tokens implement the ERC20Detailed interface.
 *      An implementation where `getUnderlying()` returns the zero address is for ETH pools.
 * @param poolName Name of the pool.
 * @param underlying Address of the pool's underlying.
 * @param lpTokenArgs Arguments to create the LP token for the pool
 * @param vaultArgs Arguments to create the vault
 * @param implementationNames Name of the implementations to use
 * @return addrs Address of the deployed pool, address of the pool's deployed LP token.
 */

function deployPool(
        string calldata poolName,
        uint256 depositCap,
        address underlying,
        LpTokenArgs calldata lpTokenArgs,
        VaultArgs calldata vaultArgs,
        ImplementationNames calldata implementationNames
    ) external onlyGovernance returns (Addresses memory addrs) {
        DeployPoolVars memory vars;

        vars.poolImplementation = implementations[_POOL_KEY][implementationNames.pool];
        require(vars.poolImplementation != address(0), Error.INVALID_POOL_IMPLEMENTATION);

        vars.lpTokenImplementation = implementations[_LP_TOKEN_KEY][implementationNames.lpToken];
        require(vars.lpTokenImplementation != address(0), Error.INVALID_LP_TOKEN_IMPLEMENTATION);

        vars.vaultImplementation = implementations[_VAULT_KEY][implementationNames.vault];
        require(vars.vaultImplementation != address(0), Error.INVALID_VAULT_IMPLEMENTATION);

        vars.stakerVaultImplementation = implementations[_STAKER_VAULT_KEY][
            implementationNames.stakerVault
        ];
        require(
            vars.stakerVaultImplementation != address(0),
            Error.INVALID_STAKER_VAULT_IMPLEMENTATION
        );

        addrs.pool = Clones.clone(vars.poolImplementation);
        addrs.vault = Clones.clone(vars.vaultImplementation);

        if (underlying == address(0)) {
            // ETH pool
            require(
                ILiquidityPool(vars.poolImplementation).getUnderlying() == address(0),
                Error.INVALID_POOL_IMPLEMENTATION
            );
            require(lpTokenArgs.decimals == 18, Error.INVALID_DECIMALS);
            IEthPool(addrs.pool).initialize(poolName, depositCap, addrs.vault);
        } else {
            IErc20Pool(addrs.pool).initialize(poolName, underlying, depositCap, addrs.vault);
        }

        addrs.lpToken = Clones.clone(vars.lpTokenImplementation);

        ILpToken(addrs.lpToken).initialize(
            lpTokenArgs.name,
            lpTokenArgs.symbol,
            lpTokenArgs.decimals,
            addrs.pool
        );

        addrs.stakerVault = Clones.clone(vars.stakerVaultImplementation);
        IStakerVault(addrs.stakerVault).initialize(addrs.lpToken);
        controller.addStakerVault(addrs.stakerVault);

        ILiquidityPool(addrs.pool).setLpToken(addrs.lpToken);
        ILiquidityPool(addrs.pool).setStaker();

        IVault(addrs.vault).initialize(
            addrs.pool,
            vaultArgs.debtLimit,
            vaultArgs.targetAllocation,
            vaultArgs.bound
        );

        addressProvider.addPool(addrs.pool);

        emit NewPool(addrs.pool, addrs.vault, addrs.lpToken, addrs.stakerVault);
        return addrs;
    }


/**
 * @notice Add a new implementation of type `name` to the factory.
 * @param key of the implementation to add.
 * @param name of the implementation to add.
 * @param implementation of lp token implementation to add.
 */

function _addImplementation(
        bytes32 key,
        bytes32 name,
        address implementation
    ) internal returns (bool) {
        mapping(bytes32 => address) storage currentImplementations = implementations[key];
        if (currentImplementations[name] != address(0)) {
            return false;
        }
        currentImplementations[name] = implementation;
        emit NewImplementation(key, name, implementation);
        return true;
    }


/**
 * @dev Crowdsale in the constructor takes addresses of
 *      the just deployed VLBToken and VLBRefundVault contracts
 * @param _tokenAddress address of the VLBToken deployed contract
 */

function VLBCrowdsale(address _tokenAddress, address _wallet, address _escrow, uint rate) public {
        require(_tokenAddress != address(0));
        require(_wallet != address(0));
        require(_escrow != address(0));

        escrow = _escrow;

        // Set initial exchange rate
        ETHUSD = rate;

        // VLBTokenwas deployed separately
        token = Token(_tokenAddress);

        vault = new VLBRefundVault(_wallet);
        bonuses = new VLBBonusStore();
    }


/**
 * @dev main function to buy tokens
 * @param beneficiary target wallet for tokens can vary from the sender one
 */

function buyTokens(address beneficiary) whenNotPaused public payable {
        require(beneficiary != address(0));
        require(validPurchase(msg.value));

        uint256 weiAmount = msg.value;

        // buyer and beneficiary could be two different wallets
        address buyer = msg.sender;

        weiRaised = weiRaised.add(weiAmount);

        // calculate token amount to be created
        uint256 tokens = weiAmount.mul(getConversionRate());

        uint8 rate = bonuses.collectRate(beneficiary);
        if (rate != 0) {
            tokens = tokens.mul(rate).div(100);
        }

        if (!token.transferFrom(token.tokensWallet(), beneficiary, tokens)) {
            revert();
        }

        TokenPurchase(buyer, beneficiary, weiAmount, tokens);

        vault.deposit.value(weiAmount)(buyer);
    }


/**
 * @dev check if the current purchase valid based on time and amount of passed ether
 * @param _value amount of passed ether
 * @return true if investors can buy at the moment
 */

function validPurchase(uint256 _value) internal constant returns (bool) {
        bool nonZeroPurchase = _value != 0;
        bool withinPeriod = now >= startTime && now <= endTime;
        bool withinCap = !capReached(weiRaised.add(_value));

        // For presale we want to decline all payments less then minPresaleAmount
        bool withinAmount = msg.value >= MIN_SALE_AMOUNT;

        return nonZeroPurchase && withinPeriod && withinCap && withinAmount;
    }


/**
 * @dev returns current token price based on current presale time frame
 */

function getConversionRate() public constant returns (uint256) {
        if (now >= startTime + 106 days) {
            return 650;
        } else if (now >= startTime + 99 days) {
            return 676;
        } else if (now >= startTime + 92 days) {
            return 715;
        } else if (now >= startTime + 85 days) {
            return 780;
        } else if (now >= startTime) {
            return 845;
        }
        return 0;
    }


/**
 * ERC677 transferandcall support
 */

function onTokenTransfer(address _sender, uint _value, bytes calldata _data) external {
        // make sure that only chainlink transferandcalls are supported
        require(msg.sender == tokenAddress);

        // convert _data to address
        bytes memory x = _data;
        address _referredBy;

        assembly {
            _referredBy := mload(add(x,20))
        }

        purchaseTokens(_value, _referredBy, _sender);
    }