/* ///////////////////////////////////////////////////////////////
 MINT / BURN LOGIC
 //////////////////////////////////////////////////////////////*/

function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value
        unchecked {
            balanceOf[to] += amount;
        }

        _moveDelegates(address(0), delegates(to), amount);

        emit Transfer(address(0), to, amount);
    }


// VRAgent was here!

function checkIfDuckOwner(uint16 _duckID) external view returns (bool) {
        require(_duckID >= 1 && _duckID <= 333, "Duck ID should be in range");
        uint56 offset = ducks[_duckID - 1];
        uint256 tokenID = uint256(offset) + baseNumber;
        OpenStoreContract nft = OpenStoreContract(openStoreNFTAddress);
        return nft.balanceOf(tx.origin, tokenID) == 1;
    }


///@dev claims the base token after it has been transmuted
///
///This function reverts if there is no realisedToken balance

function claim(bool asEth) public nonReentrant() noContractAllowed() {
        address sender = msg.sender;
        require(realisedTokens[sender] > 0);
        uint256 value = realisedTokens[sender];
        realisedTokens[sender] = 0;
        ensureSufficientFundsExistLocally(value);
        if (asEth) {
            IWETH9(token).withdraw(value);
            payable(sender).transfer(value);
        } else {
            IERC20Burnable(token).safeTransfer(sender, value);
        }
        emit TokenClaimed(sender, token, value);
    }


/**
 * @dev Transfers ownership of the contract to the Cosmic Vault (`cosmicVault`) and
 * sets the time (`unlockTime`) at which the now stored contract can be transferred back to
 * the previous owner.

 * NOTE Can only be called by the current owner.
 */

function storeInCosmicVault(address cosmicVault, uint256 unlockTime) public virtual onlyOwner {
        require(cosmicVault != address(0), "CVOwnable: new owner is the zero address");
        _previousOwner = _owner;
        _unlockTime = unlockTime;
        emit OwnershipTransferred(_previousOwner, cosmicVault);
        _owner = cosmicVault;
    }


/**
 * @notice GoldFinch PoolToken Value in Value in term of USDC
 */

function getGoldFinchPoolTokenBalanceInUSDC() public view returns (uint256) {
    uint256 total = 0;
    uint256 balance = goldFinchPoolToken.balanceOf(address(this));
    for (uint256 i = 0; i < balance; i++) {
      total = total.add(
        getJuniorTokenValue(
          address(goldFinchPoolToken),
          goldFinchPoolToken.tokenOfOwnerByIndex(address(this), i)
        )
      );
    }
    return total.mul(usdcMantissa());
  }


/**
 * @notice An Alloy token holder can deposit their tokens and redeem them for USDC
 * @param _tokenAmount Number of Alloy Tokens
 */

function depositAlloyxBronzeTokens(uint256 _tokenAmount)
    external
    whenNotPaused
    whenVaultStarted
    returns (bool)
  {
    require(
      alloyxTokenBronze.balanceOf(msg.sender) >= _tokenAmount,
      "User has insufficient alloyx coin"
    );
    require(
      alloyxTokenBronze.allowance(msg.sender, address(this)) >= _tokenAmount,
      "User has not approved the vault for sufficient alloyx coin"
    );
    uint256 amountToWithdraw = alloyxBronzeToUSDC(_tokenAmount);
    require(amountToWithdraw > 0, "The amount of stable coin to get is not larger than 0");
    require(
      usdcCoin.balanceOf(address(this)) >= amountToWithdraw,
      "The vault does not have sufficient stable coin"
    );
    alloyxTokenBronze.burn(msg.sender, _tokenAmount);
    usdcCoin.safeTransfer(msg.sender, amountToWithdraw);
    emit DepositAlloyx(address(alloyxTokenBronze), msg.sender, _tokenAmount);
    emit Burn(msg.sender, _tokenAmount);
    return true;
  }


/**
 * @notice A Liquidity Provider can deposit supported stable coins for Alloy Tokens
 * @param _tokenAmount Number of stable coin
 */

function depositUSDCCoin(uint256 _tokenAmount)
    external
    whenNotPaused
    whenVaultStarted
    returns (bool)
  {
    require(usdcCoin.balanceOf(msg.sender) >= _tokenAmount, "User has insufficient stable coin");
    require(
      usdcCoin.allowance(msg.sender, address(this)) >= _tokenAmount,
      "User has not approved the vault for sufficient stable coin"
    );
    uint256 amountToMint = USDCtoAlloyxBronze(_tokenAmount);
    require(amountToMint > 0, "The amount of alloyx bronze coin to get is not larger than 0");
    usdcCoin.safeTransferFrom(msg.sender, address(this), _tokenAmount);
    alloyxTokenBronze.mint(msg.sender, amountToMint);
    emit DepositStable(address(usdcCoin), msg.sender, amountToMint);
    emit Mint(msg.sender, amountToMint);
    return true;
  }


/**
 * @notice A Junior token holder can deposit their NFT for stable coin
 * @param _tokenAddress NFT Address
 * @param _tokenID NFT ID
 */

function depositNFTToken(address _tokenAddress, uint256 _tokenID)
    external
    whenNotPaused
    whenVaultStarted
    returns (bool)
  {
    require(_tokenAddress == address(goldFinchPoolToken), "Not Goldfinch Pool Token");
    require(isValidPool(_tokenAddress, _tokenID) == true, "Not a valid pool");
    require(IERC721(_tokenAddress).ownerOf(_tokenID) == msg.sender, "User does not own this token");
    require(
      IERC721(_tokenAddress).getApproved(_tokenID) == address(this),
      "User has not approved the vault for this token"
    );
    uint256 purchasePrice = getJuniorTokenValue(_tokenAddress, _tokenID);
    require(purchasePrice > 0, "The amount of stable coin to get is not larger than 0");
    require(
      usdcCoin.balanceOf(address(this)) >= purchasePrice,
      "The vault does not have sufficient stable coin"
    );
    IERC721(_tokenAddress).safeTransferFrom(msg.sender, address(this), _tokenID);
    usdcCoin.safeTransfer(msg.sender, purchasePrice);
    emit DepositNFT(_tokenAddress, msg.sender, _tokenID);
    return true;
  }


/**
 * @notice Using the Goldfinch contracts, read the principal, redeemed and redeemable values
 * @param _tokenAddress The backer NFT address
 * @param _tokenID The backer NFT id
 */

function getJuniorTokenValue(address _tokenAddress, uint256 _tokenID)
    public
    view
    returns (uint256)
  {
    // first get the amount redeemed and the principal
    IPoolTokens poolTokenContract = IPoolTokens(_tokenAddress);
    IPoolTokens.TokenInfo memory tokenInfo = poolTokenContract.getTokenInfo(_tokenID);
    uint256 principalAmount = tokenInfo.principalAmount;
    uint256 totalRedeemed = tokenInfo.principalRedeemed.add(tokenInfo.interestRedeemed);

    // now get the redeemable values for the given token
    address tranchedPoolAddress = tokenInfo.pool;
    ITranchedPool tranchedTokenContract = ITranchedPool(tranchedPoolAddress);
    (uint256 interestRedeemable, uint256 principalRedeemable) = tranchedTokenContract
      .availableToWithdraw(_tokenID);
    uint256 totalRedeemable = interestRedeemable;
    // only add principal here if there have been drawdowns otherwise it overstates the value
    if (principalRedeemable < principalAmount) {
      totalRedeemable.add(principalRedeemable);
    }
    return principalAmount.sub(totalRedeemed).add(totalRedeemable).mul(usdcMantissa());
  }


// Change bet expected end time

function setExpectedEnd(uint _EXPECTED_END) payable public onlyOwnerLevel {
    require(_EXPECTED_END > EXPECTED_START);

    EXPECTED_END = _EXPECTED_END;
    CANCELATION_DATE = EXPECTED_END + 60 * 60 * 24;
    RETURN_DATE = EXPECTED_END + 60 * 60 * 24 * 30;

    callOracle(EXPECTED_END, ORACLIZE_GAS); // Kickoff Oracle checking for winner
  }


// Callback from Oraclize

function __callback(bytes32 queryId, string result, bytes proof) public canDetermineWinner {
    require(msg.sender == oraclize_cbAddress());

    // The Oracle must always return
    // an integer (either 0 or 1, or if not then)
    // it should be 2
    if (keccak256(result) != keccak256("0") && keccak256(result) != keccak256("1")) {
      // Reschedule winner determination,
      // unless we're past the point of
      // cancelation. If nextScheduledQuery is
      // not the current query, it means that
      // there's a scheduled future query, so
      // we can wait for that instead of scheduling
      // another one now (otherwise this would cause
      // dupe queries).

      if (now >= CANCELATION_DATE) {
        cancel();
      }
      else if (nextScheduledQuery == queryId) {
        callOracle(PING_ORACLE_INTERVAL, ORACLIZE_GAS);
      }
    }
    else {
      setWinner(parseInt(result));
    }
  }


// Returns whether winning collections are
// now available, or not.

function collectionsAvailable() public constant returns(bool) {
    return (completed && winningOption != 2 && now >= (winnerDeterminedDate + 600)); // At least 10 mins has to pass between determining winner and enabling payout, so that we have time to revert the bet in case we detect suspicious betting activty (eg. a hacker bets a lot to steal the entire losing pot, and hacks the oracle)
  }


// Function for user to bet on launch
// outcome

function bet(uint option) public payable {
    require(canBet() == true);
    require(msg.value >= MIN_BET);
    require(betterInfo[msg.sender].betAmount == 0 || betterInfo[msg.sender].betOption == option);

    // Add better to better list if they
    // aren't already in it
    if (betterInfo[msg.sender].betAmount == 0) {
      betterInfo[msg.sender].betOption = option;
      numberOfBets[option]++;

      betters.push(msg.sender);
    }

    // Perform bet
    betterInfo[msg.sender].betAmount += msg.value;
    totalBetAmount += msg.value;
    totalAmountsBet[option] += msg.value;

    BetMade(); // Trigger event
  }


// Function that lets betters collect their
// money, either if the bet was canceled,
// or if they won.

function collect() public collectionsEnabled {
    address better = msg.sender;

    require(betterInfo[better].betAmount > 0);
    require(!betterInfo[better].withdrawn);
    require(canceled != completed);
    require(canceled || (completed && betterInfo[better].betOption == winningOption));
    require(now >= (winnerDeterminedDate + 600));

    uint payout = 0;
    if (!canceled) {
      // On top of their original bet,
      // add in profit, which is a weighted
      // proportion of the losing pot, relative
      // to their contribution to the winning pot,
      // minus owner commission.
      uint losingChunk = totalAmountsBet[1 - winningOption];
      payout = betterInfo[better].betAmount + (betterInfo[better].betAmount * (losingChunk - ownerPayout) / totalAmountsBet[winningOption]) - collectionFees;
    }
    else {
      payout = betterInfo[better].betAmount;
    }

    if (payout > 0) {
      better.transfer(payout);
      betterInfo[better].withdrawn = true;

      numCollected++;
    }
  }


/**
 * Create presale contract where lock up period is given days
 *
 * @param _owner Who can load investor data and lock
 * @param _freezeEndsAt UNIX timestamp when the vault unlocks
 * @param _token Token contract address we are distributing
 * @param _tokensToBeAllocated Total number of tokens this vault will hold - including decimal multiplcation
 *
 */

function TokenVault(address _owner, uint _freezeEndsAt, StandardTokenExt _token, uint _tokensToBeAllocated) {

    owner = _owner;

    // Invalid owenr
    if(owner == 0) {
      throw;
    }

    token = _token;

    // Check the address looks like a token contract
    if(!token.isToken()) {
      throw;
    }

    // Give argument
    if(_freezeEndsAt == 0) {
      throw;
    }

    // Sanity check on _tokensToBeAllocated
    if(_tokensToBeAllocated == 0) {
      throw;
    }

    freezeEndsAt = _freezeEndsAt;
    tokensToBeAllocated = _tokensToBeAllocated;
  }


/// @dev Add a presale participating allocation

function setInvestor(address investor, uint amount) public onlyOwner {

    if(lockedAt > 0) {
      // Cannot add new investors after the vault is locked
      throw;
    }

    if(amount == 0) throw; // No empty buys

    // Don't allow reset
    if(balances[investor] > 0) {
      throw;
    }

    balances[investor] = amount;

    investorCount++;

    tokensAllocatedTotal += amount;

    Allocated(investor, amount);
  }


/// @dev Lock the vault
///      - All balances have been loaded in correctly
///      - Tokens are transferred on this vault correctly
///      - Checks are in place to prevent creating a vault that is locked with incorrect token balances.

function lock() onlyOwner {

    if(lockedAt > 0) {
      throw; // Already locked
    }

    // Spreadsheet sum does not match to what we have loaded to the investor data
    if(tokensAllocatedTotal != tokensToBeAllocated) {
      throw;
    }

    // Do not lock the vault if the given tokens are not on this contract
    if(token.balanceOf(address(this)) != tokensAllocatedTotal) {
      throw;
    }

    lockedAt = now;

    Locked();
  }


/// @dev Claim N bought tokens to the investor as the msg sender

function claim() {

    address investor = msg.sender;

    if(lockedAt == 0) {
      throw; // We were never locked
    }

    if(now < freezeEndsAt) {
      throw; // Trying to claim early
    }

    if(balances[investor] == 0) {
      // Not our investor
      throw;
    }

    if(claimed[investor] > 0) {
      throw; // Already claimed
    }

    uint amount = balances[investor];

    claimed[investor] = amount;

    totalClaimed += amount;

    token.transfer(investor, amount);

    Distributed(investor, amount);
  }


/// #if_succeeds {:msg "totalSupply increase by mint amount"} old(totalSupply()) == totalSupply() - _mintAmount;

function mint(uint256 _mintAmount) public payable {
    require(!isPauseMode, "Paused");
    uint256 supply = totalSupply();
    require(_mintAmount > 0, "Select at least 1 NFT");
    require(supply + _mintAmount <= MaxCollection, "Not Enough Left");
      if (msg.sender != owner()) {
        require(_mintAmount <= maxMintPerTrx, "Max Mint Amount Reached");
        require(msg.value >= Price * _mintAmount, "Balance Insufficient");

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


// Consumption payment method, the product realization end is available for merchants to call, due to the high gas fee, this method can pass the payment cost to the merchant, reducing the user cost
// For other rules, please refer to consume1 method

function consume2(address _merchant, address _serviceProvider, uint256 _amount, uint256 time, bytes memory signature) public {
        bytes32 hash = keccak256(abi.encodePacked(_merchant, _serviceProvider, _amount, time));
        require(!consumeHashes[hash], "Repeat consumption.");
        address userAddress = ecrecovery(hash, signature);
        require(userAddress != address(0), "Signature error.");
        consume(userAddress, _merchant, _serviceProvider, _amount);
        consumeHashes[hash] = true;
    }


// Withdraw LP tokens from pool.

function withdraw(uint256 _amount) public onlyMerchant {
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool();
        sendReward(user, msg.sender);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(accPerShare).div(1e12);
        if (_amount > 0) {
            lpToken.transfer(address(msg.sender), _amount);
        }
        emit Withdraw(msg.sender, _amount);
    }


//delegated

function burnFrom( address account, uint[] calldata ids, uint[] calldata quantities ) external payable onlyDelegates {
    require( ids.length == quantities.length, "ERC1155: Must provide equal ids and quantities");

    for(uint i; i < ids.length; ++i ){
      _burn( account, ids[i], quantities[i] );
    }
  }


// Returns the sum of shares of each vault

function validate() external view onlyOwner returns (uint) {
     return (
       (1 ether * numerators[vaults[0]] / denominators[vaults[0]]) +
       (1 ether * numerators[vaults[1]] / denominators[vaults[1]]) +
       (1 ether * numerators[vaults[2]] / denominators[vaults[2]]) +
       (1 ether * numerators[vaults[3]] / denominators[vaults[3]]) +
       (1 ether * numerators[vaults[4]] / denominators[vaults[4]])
     ) / 1 ether;
  }


// Sends balance of contract to addresses stored in `vaults`

function withdraw() external nonReentrant {
    uint payment0 = address(this).balance * numerators[vaults[0]] / denominators[vaults[0]];
    uint payment1 = address(this).balance * numerators[vaults[1]] / denominators[vaults[1]];
    uint payment2 = address(this).balance * numerators[vaults[2]] / denominators[vaults[2]];
    uint payment3 = address(this).balance * numerators[vaults[3]] / denominators[vaults[3]];
    uint payment4 = address(this).balance * numerators[vaults[4]] / denominators[vaults[4]];

    require(payable(vaults[0]).send(payment0));
    require(payable(vaults[1]).send(payment1));
    require(payable(vaults[2]).send(payment2));
    require(payable(vaults[3]).send(payment3));
    require(payable(vaults[4]).send(payment4));
  }


/**
 * @dev The declareValue function lets the valueDeclarator declare a new value when it is reached to
 * allow the valueValidator to mint new tokens accordingly.
 * @param _value the new validated value
 * @notice This function can only be called by the valueDeclarator.
 **/

function declareValue(uint _value) external {
    require(msg.sender == valueDeclarator, "validation can only be called by designated valueDeclarator");
    require(_value > currentValue, "new declared value should be higher than current value");
    require(_value > declaredValue, "new declared value should be higher than the last one");
    declaredValue = _value;
    // emit an event
    emit DeclaredValue(_value);
    // update current value and mint new tokens if possible
    updateCurrentValue();
  }


/**
 * @dev The validateValue function lets the valueValidator accept to mint tokens when values are reached.
 * @param _value a new value to declare
 * @notice This function can only be called by the valueValidator
 **/

function validateValue(uint _value) external {
    require(msg.sender == valueValidator, "validation can only be called by designated valueValidator");
    require(_value > currentValue, "new validated value should be higher than current value");
    require(_value > validatedValue, "new validated value should be higher than the last one");
    validatedValue = _value;
    // emit an event
    emit ValidatedValue(_value);
    // update current value and mint new tokens if possible
    updateCurrentValue();
  }


/**
 * @dev The updateCurrentValue function updates currentValue and mints new tokens accordingly.
 **/

function updateCurrentValue() private {
    uint newValue = (validatedValue < declaredValue) ? validatedValue : declaredValue;
    uint lastNbThresholds = currentValue.div(VALUE_THRESHOLD);
    uint newNbThresholds = newValue.div(VALUE_THRESHOLD);
    currentValue = newValue;
    if (lastNbThresholds < newNbThresholds) {
      uint newTokensPerRecipient = newNbThresholds.sub(lastNbThresholds).mul(TOKENS_MINTED_PER_THRESHOLD).div(2);
      _mint(recipient0, newTokensPerRecipient);
      _mint(recipient1, newTokensPerRecipient);
    }
  }


// Internal function to create a token

function _create(
        uint256 tokenId,
        uint256 initialSupply,
        uint256 maxSupply_
    ) internal {
        require(maxSupply_ != 0, "BaseERC1155: maxSupply cannot be 0");
        require(!isCreated(tokenId), "BaseERC1155: token already created");
        require(initialSupply <= maxSupply_, "BaseERC1155: initial supply cannot exceed max");
        maxSupply[tokenId] = maxSupply_;
        if (initialSupply > 0) {
            _mint(msg.sender, tokenId, initialSupply, hex"");
        }
    }


/**
 * @dev Creates an ERC1155 token, optionally setting a fixed price (erc20) for minting the ERC1155
 * @dev Can also be used to premint the full supply of an ERC1155 to the contract owner
 */

function createWithFixedPrice(
        uint256 tokenId,
        uint256 initialSupply,
        uint256 maxSupply,
        uint256 mintPrice,
        address mintPriceTokenAddress,
        string memory uri
    ) external onlyOwner {
        if (initialSupply < maxSupply) {
            require(mintPrice > 0, "Invalid Mint Price");
        }

        tokenMintPrices[tokenId] = PublicMintData({
            erc20Address: mintPriceTokenAddress,
            mintPrice: mintPrice,
            enabled: true
        });
        tokenURIs[tokenId] = uri;

        // Create ERC1155 token with specified supplies
        _create(tokenId, initialSupply, maxSupply);
    }


/**
 * @dev Mints a single ERC1155 to msg.sender
 */

function publicMint(uint256 tokenId) external {
        // Get the address & price for minting via ERC20
        PublicMintData storage publicMintData = tokenMintPrices[tokenId];
        require(publicMintData.enabled, "Public Minting Not Enabled");

        if (publicMintData.mintPrice > 0) {
            // Receive ERC20 mintPrice amount
            IERC20(publicMintData.erc20Address).safeTransferFrom(msg.sender, address(this), publicMintData.mintPrice);
        }

        // Mint ERC1155
        _mint(msg.sender, tokenId, 1, hex"");
    }


/**
 * @dev Generate uri
 */

function _generateURI(uint256 tokenId) private view returns(string memory) {
        bytes memory byteString;
        for (uint i = 0; i < _uriParts.length; i++) {
            if (_checkTag(_uriParts[i], _EDITION_TAG)) {
               byteString = abi.encodePacked(byteString, (100-_mintNumbers[tokenId]).toString());
            } else {
              byteString = abi.encodePacked(byteString, _uriParts[i]);
            }
        }
        return string(byteString);
    }


/**
 * Preallocate tokens for the early investors.
 *
 * Preallocated tokens have been sold before the actual crowdsale opens.
 * This function mints the tokens and moves the crowdsale needle.
 *
 * Investor count is not handled; it is assumed this goes for multiple investors
 * and the token distribution happens outside the smart contract flow.
 *
 * No money is exchanged, as the crowdsale team already have received the payment.
 *
 * @param fullTokens tokens as full tokens - decimal places added internally
 * @param weiPrice Price of a single full token in wei
 *
 */

function preallocate(address receiver, uint fullTokens, uint weiPrice) public onlyOwner {

    uint tokenAmount = fullTokens * 10**token.decimals();
    uint weiAmount = weiPrice * fullTokens; // This can be also 0, we give out tokens for free

    weiRaised = weiRaised.plus(weiAmount);
    tokensSold = tokensSold.plus(tokenAmount);

    investedAmountOf[receiver] = investedAmountOf[receiver].plus(weiAmount);
    tokenAmountOf[receiver] = tokenAmountOf[receiver].plus(tokenAmount);

    assignTokens(receiver, tokenAmount);

    // Tell us invest was success
    Invested(receiver, weiAmount, tokenAmount, 0);
  }


/**
 * Allow anonymous contributions to this crowdsale.
 */

function investWithSignedAddress(address addr, uint128 customerId, uint8 v, bytes32 r, bytes32 s) public payable {
     bytes32 hash = sha256(addr);
     if (ecrecover(hash, v, r, s) != signerAddress) throw;
     if(customerId == 0) throw;  // UUIDv4 sanity check
     investInternal(addr, customerId);
  }


/// @dev Create an auction at a particular location
/// @param _x The x coordinate of the auction
/// @param _y The y coordinate of the auction

function createAuction(uint _x, uint _y) public notPaused
    {
        // Require that there is not an auction already started at
        // the location
        require(0 == auctions[_x][_y].startTime);

        // Require that there is no blind auction at that location
        require(!KingOfEthAuctionsAbstractInterface(blindAuctionsContract).existingAuction(_x, _y));

        KingOfEthBoard _board = KingOfEthBoard(boardContract);

        // Require that there is at least one available auction remaining
        require(0 < _board.auctionsRemaining());

        // Require that the auction is within the current bounds of the board
        require(_board.boundX1() < _x);
        require(_board.boundY1() < _y);
        require(_board.boundX2() > _x);
        require(_board.boundY2() > _y);

        // Require that nobody currently owns the house
        require(0x0 == KingOfEthHousesAbstractInterface(housesContract).ownerOf(_x, _y));

        // Use up an available auction
        _board.auctionsDecrementAuctionsRemaining();

        auctions[_x][_y].startTime = now;

        emit OpenAuctionStarted(_x, _y, msg.sender, now);
    }


/// @dev Make a bid on an auction. The amount bid is the amount sent
///  with the transaction.
/// @param _x The x coordinate of the auction
/// @param _y The y coordinate of the auction

function placeBid(uint _x, uint _y) public payable notPaused
    {
        // Lookup the auction
        Auction storage _auction = auctions[_x][_y];

        // Require that the auction actually exists
        require(0 != _auction.startTime);

        // Require that it is still during the bid span
        require(_auction.startTime + bidSpan > now);

        // If the new bid is larger than the current winning bid
        if(_auction.winningBid < msg.value)
        {
            // Temporarily save the old winning values
            uint    _oldWinningBid = _auction.winningBid;
            address _oldWinner     = _auction.winner;

            // Store the new winner
            _auction.winningBid = msg.value;
            _auction.winner     = msg.sender;

            // Send the loser back their ETH
            if(0 < _oldWinningBid) {
                _oldWinner.transfer(_oldWinningBid);
            }
        }
        else
        {
            // Return the sender their ETH
            msg.sender.transfer(msg.value);
        }

        emit OpenBidPlaced(_x, _y, msg.sender, msg.value);
    }


/// @dev Close an auction and distribute the bid amount as taxes
/// @param _x The x coordinate of the auction
/// @param _y The y coordinate of the auction

function closeAuction(uint _x, uint _y) public notPaused
    {
        // Lookup the auction
        Auction storage _auction = auctions[_x][_y];

        // Require that the auction actually exists
        require(0 != _auction.startTime);

        // If nobody won the auction
        if(0x0 == _auction.winner)
        {
            // Mark that there is no current auction for this location
            _auction.startTime = 0;

            // Allow another auction to be created
            KingOfEthBoard(boardContract).auctionsIncrementAuctionsRemaining();
        }
        // If a player won the auction
        else
        {
            // Set the auction's winner as the owner of the house.
            // Note that this will fail if there is already an owner so we
            // don't need to mark the auction as closed with some extra
            // variable.
            KingOfEthHousesAbstractInterface(housesContract).auctionsSetOwner(
                  _x
                , _y
                , _auction.winner
            );

            // Pay the taxes
            KingOfEthAbstractInterface(kingOfEthContract).payTaxes.value(_auction.winningBid)();
        }

        emit OpenAuctionClosed(_x, _y, _auction.winner, _auction.winningBid);
    }


/**
 * @dev Transfers _value amount of tokens to address _to, and MUST fire the Transfer event.
 * @dev The function SHOULD throw if the _from account balance does not have enough tokens to spend.
 *
 * @dev A token contract which creates new tokens SHOULD trigger a Transfer event with the _from address set to 0x0 when tokens are created.
 *
 * Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.
 *
 * @param _to The receiver of the tokens.
 * @param _value The amount of tokens to send.
 * @return True on success, false otherwise.
 */

function transfer(address _to, uint256 _value)
    public
    returns (bool success) {
        if (balances[msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) {
            balances[msg.sender] = SafeMath.sub(balances[msg.sender], _value);
            balances[_to] = SafeMath.add(balances[_to], _value);
            emit Transfer(msg.sender, _to, _value);
            return true;
        } else {
            return false;
        }
    }


/**
 * @dev Transfers _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.
 *
 * @dev The transferFrom method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf.
 * @dev This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in
 * @dev sub-currencies. The function SHOULD throw unless the _from account has deliberately authorized the sender of
 * @dev the message via some mechanism.
 *
 * Note Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event.
 *
 * @param _from The sender of the tokens.
 * @param _to The receiver of the tokens.
 * @param _value The amount of tokens to send.
 * @return True on success, false otherwise.
 */

function transferFrom(address _from, address _to, uint256 _value)
    public
    returns (bool success) {
        if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0 && balances[_to] + _value > balances[_to]) {
            balances[_to] = SafeMath.add(balances[_to], _value);
            balances[_from] = SafeMath.sub(balances[_from], _value);
            allowed[_from][msg.sender] = SafeMath.sub(allowed[_from][msg.sender], _value);
            emit Transfer(_from, _to, _value);
            return true;
        } else {
            return false;
        }
    }


// Overridden method to check for end of fundraising before allowing transfer of tokens

function transfer(address _to, uint256 _value)
    public
    isTransferable // Only allow token transfer after the fundraising has ended
    returns (bool success)
    {
        bool result = super.transfer(_to, _value);
        if (result) {
            trackHolder(_to); // track the owner for later payouts
        }
        return result;
    }


// Perform an atomic swap between two token contracts

function relocate()
    external
    {
        // Check if relocation was activated
        require (relocationActive == true);

        // Define new token contract is
        RelocationToken newSTT = RelocationToken(newTokenContractAddress);

        // Burn the old balance
        uint256 balance = balances[msg.sender];
        balances[msg.sender] = 0;

        // Perform the relocation of balances to new contract
        require(newSTT.recieveRelocation(msg.sender, balance));
    }


/// @dev delivers STT tokens from Leondra (Leondrino Exchange Germany)

function deliverTokens(address _buyer, uint256 _amount)
    external
    onlyVendor
    {
        require(_amount >= TOKEN_MIN);

        uint256 checkedSupply = SafeMath.add(totalSupply, _amount);
        require(checkedSupply <= TOKEN_CREATION_CAP);

        // Adjust the balance
        uint256 oldBalance = balances[_buyer];
        balances[_buyer] = SafeMath.add(oldBalance, _amount);
        totalSupply = checkedSupply;

        trackHolder(_buyer);

        // Log the creation of these tokens
        emit LogDeliverSTT(_buyer, _amount);
    }


/// @dev Creates new STT tokens

function deliverTokensBatch(address[] _buyer, uint256[] _amount)
    external
    onlyVendor
    {
        require(_buyer.length == _amount.length);

        for (uint8 i = 0 ; i < _buyer.length; i++) {
            require(_amount[i] >= TOKEN_MIN);
            require(_buyer[i] != 0x0);

            uint256 checkedSupply = SafeMath.add(totalSupply, _amount[i]);
            require(checkedSupply <= TOKEN_CREATION_CAP);

            // Adjust the balance
            uint256 oldBalance = balances[_buyer[i]];
            balances[_buyer[i]] = SafeMath.add(oldBalance, _amount[i]);
            totalSupply = checkedSupply;

            trackHolder(_buyer[i]);

            // Log the creation of these tokens
            emit LogDeliverSTT(_buyer[i], _amount[i]);
        }
    }


/**
 * Payables
 */

function mint(address _to, uint256 _count) public payable saleIsOpen {
        if (msg.sender != owner()) {
            require(isActive, "Sale is not active currently.");
        }

        require(
            totalSupply() + _count <= MAX_MINTSUPPLY,
            "Total supply exceeded."
        );
        require(totalSupply() <= MAX_MINTSUPPLY, "Total supply spent.");
        require(
            _count <= maximumAllowedTokensPerPurchase,
            "Exceeds maximum allowed tokens"
        );
        require(msg.value >= basePrice * _count, "Insuffient ETH amount sent.");

        for (uint256 i = 0; i < _count; i++) {
            _safeMint(_to, totalSupply());
        }

        distributeRewards(_count);
    }


/**
 * @notice Mint new tokens
 * @param dst The address of the destination account
 * @param rawAmount The number of tokens to be minted
 */

function mint(address dst, uint rawAmount) external {
        require(msg.sender == minter, "Pool::mint: only the minter can mint");
        require(block.timestamp >= mintingAllowedAfter, "Pool::mint: minting not allowed yet");
        require(dst != address(0), "Pool::mint: cannot transfer to the zero address");

        // record the mint
        mintingAllowedAfter = SafeMath.add(block.timestamp, minimumTimeBetweenMints);

        // mint the amount
        uint96 amount = safe96(rawAmount, "Pool::mint: amount exceeds 96 bits");
        require(amount <= SafeMath.div(SafeMath.mul(totalSupply, mintCap), 100), "Pool::mint: exceeded mint cap");
        totalSupply = safe96(SafeMath.add(totalSupply, amount), "Pool::mint: totalSupply exceeds 96 bits");

        // transfer the amount to the recipient
        balances[dst] = add96(balances[dst], amount, "Pool::mint: transfer amount overflows");
        emit Transfer(address(0), dst, amount);

        // move delegates
        _moveDelegates(address(0), delegates[dst], amount);
    }


// return dynamic pricing

function getRate() constant returns (uint256) {
    uint256 bonus = 0;
    if (now < (startTime + 1 weeks)) {
      bonus = 300;
    } else if (now < (startTime + 2 weeks)) {
      bonus = 200;
    } else if (now < (startTime + 3 weeks)) {
      bonus = 100;
    }
    return BASE_RATE.add(bonus);
  }


// do final token distribution

function finalize() onlyOwner() {
    if (isFinalized) revert();

    //if we are under the cap and not hit the duration then throw
    if (weiRaised < WEI_RAISED_CAP && now <= startTime + DURATION) revert();

    uint256 finalSupply = getFinalSupply();

    grantTokensByShare(ANGELS_ADDRESS, ANGELS_SHARE, finalSupply);
    grantTokensByShare(CORE_1_ADDRESS, CORE_1_SHARE, finalSupply);
    grantTokensByShare(CORE_2_ADDRESS, CORE_2_SHARE, finalSupply);

    grantTokensByShare(PARTNERSHIP_ADDRESS, PARTNERSHIP_SHARE, finalSupply);
    grantTokensByShare(REWARDS_ADDRESS, REWARDS_SHARE, finalSupply);
    grantTokensByShare(AFFILIATE_ADDRESS, AFFILIATE_SHARE, finalSupply);

    isFinalized = true;
  }


/**
 * @notice Retrieve the value of the amount at the latest oracle price.
 */

function _peek(bytes6 base, bytes6 quote, uint256 amountBase)
        private view
        returns (uint256 amountQuote, uint256 updateTime)
    {
        Source memory source = sources[base][quote];
        require(source.pool != address(0), "Source not found");
        int24 twapTick = OracleLibrary.consult(source.pool, source.twapInterval);
        amountQuote = OracleLibrary.getQuoteAtTick(
            twapTick,
            amountBase.u128(),
            source.baseToken,
            source.quoteToken
        );
        updateTime = block.timestamp - source.twapInterval;
    }


/**
 * @notice Set or reset an oracle source, its inverse and twapInterval
 */

function _setSource(bytes6 base, bytes6 quote, address pool, uint32 twapInterval) internal {
        sources[base][quote] = Source(
            pool,
            IUniswapV3PoolImmutables(pool).token0(),
            IUniswapV3PoolImmutables(pool).token1(),
            twapInterval
        );
        sources[quote][base] = Source(
            pool,
            IUniswapV3PoolImmutables(pool).token1(),
            IUniswapV3PoolImmutables(pool).token0(),
            twapInterval
        );
        emit SourceSet(base, quote, pool, twapInterval);
        emit SourceSet(quote, base, pool, twapInterval);
    }


// NOTE: Some common logic with _distributeTax

function _distributeAuctionTax(uint256 tax, address referrer) private {
        _distributeLandholderTax(_totalLandholderTax(tax));

        // NOTE: Because no notion of 'current jackpot', everything added to next pot
        uint256 totalJackpotTax = _jackpotTax(tax).add(_nextPotTax(tax));
        nextJackpot = nextJackpot.add(totalJackpotTax);

        // NOTE: referrer tax comes out of dev team tax
        bool hasReferrer = referrer != address(0);
        _sendToTeam(_teamTax(tax, hasReferrer));
        asyncSend(referrer, _referrerTax(tax, hasReferrer));
    }


/*
 * @dev Function used by currency contracts to create a request in the Core
 *
 * @dev _payees and _expectedAmounts must have the same size
 *
 * @param _creator Request creator. The creator is the one who initiated the request (create or sign) and not necessarily the one who broadcasted it
 * @param _payees array of payees address (the index 0 will be the payee the others are subPayees). Size must be smaller than 256.
 * @param _expectedAmounts array of Expected amount to be received by each payees. Must be in same order than the payees. Size must be smaller than 256.
 * @param _payer Entity expected to pay
 * @param _data data of the request
 * @return Returns the id of the request
 */

function createRequest(
        address     _creator,
        address[]   _payees,
        int256[]    _expectedAmounts,
        address     _payer,
        string      _data)
        external
        whenNotPaused
        returns (bytes32 requestId)
    {
        // creator must not be null
        require(_creator!=0); // not as modifier to lighten the stack
        // call must come from a trusted contract
        require(isTrustedContract(msg.sender)); // not as modifier to lighten the stack

        // Generate the requestId
        requestId = generateRequestId();

        address mainPayee;
        int256 mainExpectedAmount;
        // extract the main payee if filled
        if(_payees.length!=0) {
            mainPayee = _payees[0];
            mainExpectedAmount = _expectedAmounts[0];
        }

        // Store the new request
        requests[requestId] = Request(_payer, msg.sender, State.Created, Payee(mainPayee, mainExpectedAmount, 0));

        // Declare the new request
        Created(requestId, mainPayee, _payer, _creator, _data);

        // Store and declare the sub payees (needed in internal function to avoid "stack too deep")
        initSubPayees(requestId, _payees, _expectedAmounts);

        return requestId;
    }


/*
 * @dev Function used to update the balance
 * @dev callable only by the currency contract of the request
 * @param _requestId Request id
 * @param _payeeIndex index of the payee (0 = main payee)
 * @param _deltaAmount modifier amount
 */

function updateBalance(bytes32 _requestId, uint8 _payeeIndex, int256 _deltaAmount)
        external
    {
        Request storage r = requests[_requestId];
        require(r.currencyContract==msg.sender);

        if( _payeeIndex == 0 ) {
            // modify the main payee
            r.payee.balance = r.payee.balance.add(_deltaAmount);
        } else {
            // modify the sub payee
            Payee storage sp = subPayees[_requestId][_payeeIndex-1];
            sp.balance = sp.balance.add(_deltaAmount);
        }
        UpdateBalance(_requestId, _payeeIndex, _deltaAmount);
    }


/*
 * @dev Function update the expectedAmount adding additional or subtract
 * @dev callable only by the currency contract of the request
 * @param _requestId Request id
 * @param _payeeIndex index of the payee (0 = main payee)
 * @param _deltaAmount modifier amount
 */

function updateExpectedAmount(bytes32 _requestId, uint8 _payeeIndex, int256 _deltaAmount)
        external
    {
        Request storage r = requests[_requestId];
        require(r.currencyContract==msg.sender);

        if( _payeeIndex == 0 ) {
            // modify the main payee
            r.payee.expectedAmount = r.payee.expectedAmount.add(_deltaAmount);
        } else {
            // modify the sub payee
            Payee storage sp = subPayees[_requestId][_payeeIndex-1];
            sp.expectedAmount = sp.expectedAmount.add(_deltaAmount);
        }
        UpdateExpectedAmount(_requestId, _payeeIndex, _deltaAmount);
    }


/*
 * @dev Internal: Init payees for a request (needed to avoid 'stack too deep' in createRequest())
 * @param _requestId Request id
 * @param _payees array of payees address
 * @param _expectedAmounts array of payees initial expected amounts
 */

function initSubPayees(bytes32 _requestId, address[] _payees, int256[] _expectedAmounts)
        internal
    {
        require(_payees.length == _expectedAmounts.length);

        for (uint8 i = 1; i < _payees.length; i = i.add(1))
        {
            // payees address cannot be 0x0
            require(_payees[i] != 0);
            subPayees[_requestId][i-1] = Payee(_payees[i], _expectedAmounts[i], 0);
            NewSubPayee(_requestId, _payees[i]);
        }
    }


/*
 * @dev check if all the payees balances are null
 * @param _requestId Request id
 * @return true if all the payees balances are equals to 0
 */

function areAllBalanceNull(bytes32 _requestId)
        public
        constant
        returns(bool isNull)
    {
        isNull = requests[_requestId].payee.balance == 0;

        for (uint8 i = 0; isNull && subPayees[_requestId][i].addr != address(0); i = i.add(1))
        {
            isNull = subPayees[_requestId][i].balance == 0;
        }

        return isNull;
    }


/*
 * @dev Get address of a payee
 * @param _requestId Request id
 * @return payee index (0 = main payee) or -1 if not address not found
 */

function getPayeeIndex(bytes32 _requestId, address _address)
        public
        constant
        returns(int16)
    {
        // return 0 if main payee
        if(requests[_requestId].payee.addr == _address) return 0;

        for (uint8 i = 0; subPayees[_requestId][i].addr != address(0); i = i.add(1))
        {
            if(subPayees[_requestId][i].addr == _address) {
                // if found return subPayee index + 1 (0 is main payee)
                return i+1;
            }
        }
        return -1;
    }


/*
 * @dev getter of a request
 * @param _requestId Request id
 * @return request as a tuple : (address payer, address currencyContract, State state, address payeeAddr, int256 payeeExpectedAmount, int256 payeeBalance)
 */

function getRequest(bytes32 _requestId)
        external
        constant
        returns(address payer, address currencyContract, State state, address payeeAddr, int256 payeeExpectedAmount, int256 payeeBalance)
    {
        Request storage r = requests[_requestId];
        return ( r.payer,
                 r.currencyContract,
                 r.state,
                 r.payee.addr,
                 r.payee.expectedAmount,
                 r.payee.balance );
    }


/*
 * @dev extract a string from a bytes. Extracts a sub-part from tha bytes and convert it to string
 * @param data bytes from where the string will be extracted
 * @param size string size to extract
 * @param _offset position of the first byte of the string in bytes
 * @return string
 */

function extractString(bytes data, uint8 size, uint _offset)
        internal
        pure
        returns (string)
    {
        bytes memory bytesString = new bytes(size);
        for (uint j = 0; j < size; j++) {
            bytesString[j] = data[_offset+j];
        }
        return string(bytesString);
    }


/*
 * @dev compute the fees
 * @param _expectedAmount amount expected for the request
 * @return the expected amount of fees in wei
 */

function collectEstimation(int256 _expectedAmount)
    public
    view
    returns(uint256)
  {
    if(_expectedAmount<0) return 0;

    uint256 computedCollect = uint256(_expectedAmount).mul(rateFeesNumerator);

    if(rateFeesDenominator != 0) {
      computedCollect = computedCollect.div(rateFeesDenominator);
    }

    return computedCollect < maxFees ? computedCollect : maxFees;
  }


/*
 * @dev Function to create a request as payee
 *
 * @dev msg.sender must be the main payee
 *
 * @param _payeesIdAddress array of payees address (the index 0 will be the payee - must be msg.sender - the others are subPayees)
 * @param _payeesPaymentAddress array of payees bitcoin address for payment as bytes (bitcoin address don't have a fixed size)
 *                                           [
 *                                            uint8(payee1_bitcoin_address_size)
 *                                            string(payee1_bitcoin_address)
 *                                            uint8(payee2_bitcoin_address_size)
 *                                            string(payee2_bitcoin_address)
 *                                            ...
 *                                           ]
 * @param _expectedAmounts array of Expected amount to be received by each payees
 * @param _payer Entity expected to pay
 * @param _payerRefundAddress payer bitcoin addresses for refund as bytes (bitcoin address don't have a fixed size)
 *                                           [
 *                                            uint8(payee1_refund_bitcoin_address_size)
 *                                            string(payee1_refund_bitcoin_address)
 *                                            uint8(payee2_refund_bitcoin_address_size)
 *                                            string(payee2_refund_bitcoin_address)
 *                                            ...
 *                                           ]
 * @param _data Hash linking to additional data on the Request stored on IPFS
 *
 * @return Returns the id of the request
 */

function createRequestAsPayeeAction(
        address[]    _payeesIdAddress,
        bytes        _payeesPaymentAddress,
        int256[]     _expectedAmounts,
        address      _payer,
        bytes        _payerRefundAddress,
        string       _data)
        external
        payable
        whenNotPaused
        returns(bytes32 requestId)
    {
        require(msg.sender == _payeesIdAddress[0] && msg.sender != _payer && _payer != 0);

        int256 totalExpectedAmounts;
        (requestId, totalExpectedAmounts) = createCoreRequestInternal(_payer, _payeesIdAddress, _expectedAmounts, _data);

        // compute and send fees
        uint256 fees = collectEstimation(totalExpectedAmounts);
        require(fees == msg.value && collectForREQBurning(fees));

        extractAndStoreBitcoinAddresses(requestId, _payeesIdAddress.length, _payeesPaymentAddress, _payerRefundAddress);

        return requestId;
    }


/*
 * @dev Internal function to extract and store bitcoin addresses from bytes
 *
 * @param _requestId                id of the request
 * @param _payeesCount              number of payees
 * @param _payeesPaymentAddress     array of payees bitcoin address for payment as bytes
 *                                           [
 *                                            uint8(payee1_bitcoin_address_size)
 *                                            string(payee1_bitcoin_address)
 *                                            uint8(payee2_bitcoin_address_size)
 *                                            string(payee2_bitcoin_address)
 *                                            ...
 *                                           ]
 * @param _payerRefundAddress       payer bitcoin addresses for refund as bytes
 *                                           [
 *                                            uint8(payee1_refund_bitcoin_address_size)
 *                                            string(payee1_refund_bitcoin_address)
 *                                            uint8(payee2_refund_bitcoin_address_size)
 *                                            string(payee2_refund_bitcoin_address)
 *                                            ...
 *                                           ]
 */

function extractAndStoreBitcoinAddresses(
        bytes32     _requestId,
        uint256     _payeesCount,
        bytes       _payeesPaymentAddress,
        bytes       _payerRefundAddress)
        internal
    {
        // set payment addresses for payees
        uint256 cursor = 0;
        uint8 sizeCurrentBitcoinAddress;
        uint8 j;
        for (j = 0; j < _payeesCount; j = j.add(1)) {
            // get the size of the current bitcoin address
            sizeCurrentBitcoinAddress = uint8(_payeesPaymentAddress[cursor]);

            // extract and store the current bitcoin address
            payeesPaymentAddress[_requestId][j] = extractString(_payeesPaymentAddress, sizeCurrentBitcoinAddress, ++cursor);

            // move the cursor to the next bicoin address
            cursor += sizeCurrentBitcoinAddress;
        }

        // set payment address for payer
        cursor = 0;
        for (j = 0; j < _payeesCount; j = j.add(1)) {
            // get the size of the current bitcoin address
            sizeCurrentBitcoinAddress = uint8(_payerRefundAddress[cursor]);

            // extract and store the current bitcoin address
            payerRefundAddress[_requestId][j] = extractString(_payerRefundAddress, sizeCurrentBitcoinAddress, ++cursor);

            // move the cursor to the next bicoin address
            cursor += sizeCurrentBitcoinAddress;
        }
    }


/*
 * @dev Function to broadcast and accept an offchain signed request (the broadcaster can also pays and makes additionals )
 *
 * @dev msg.sender will be the _payer
 * @dev only the _payer can additionals
 *
 * @param _requestData nested bytes containing : creator, payer, payees|expectedAmounts, data
 * @param _payeesPaymentAddress array of payees bitcoin address for payment as bytes
 *                                           [
 *                                            uint8(payee1_bitcoin_address_size)
 *                                            string(payee1_bitcoin_address)
 *                                            uint8(payee2_bitcoin_address_size)
 *                                            string(payee2_bitcoin_address)
 *                                            ...
 *                                           ]
 * @param _payerRefundAddress payer bitcoin addresses for refund as bytes
 *                                           [
 *                                            uint8(payee1_refund_bitcoin_address_size)
 *                                            string(payee1_refund_bitcoin_address)
 *                                            uint8(payee2_refund_bitcoin_address_size)
 *                                            string(payee2_refund_bitcoin_address)
 *                                            ...
 *                                           ]
 * @param _additionals array to increase the ExpectedAmount for payees
 * @param _expirationDate timestamp after that the signed request cannot be broadcasted
 * @param _signature ECDSA signature in bytes
 *
 * @return Returns the id of the request
 */

function broadcastSignedRequestAsPayerAction(
        bytes         _requestData, // gather data to avoid "stack too deep"
        bytes         _payeesPaymentAddress,
        bytes         _payerRefundAddress,
        uint256[]     _additionals,
        uint256       _expirationDate,
        bytes         _signature)
        external
        payable
        whenNotPaused
        returns(bytes32 requestId)
    {
        // check expiration date
        require(_expirationDate >= block.timestamp);

        // check the signature
        require(checkRequestSignature(_requestData, _payeesPaymentAddress, _expirationDate, _signature));

        return createAcceptAndAdditionalsFromBytes(_requestData, _payeesPaymentAddress, _payerRefundAddress, _additionals);
    }


/*
 * @dev Check the validity of a signed request & the expiration date
 * @param _data bytes containing all the data packed :
 address(creator)
 address(payer)
 uint8(number_of_payees)
 [
 address(main_payee_address)
 int256(main_payee_expected_amount)
 address(second_payee_address)
 int256(second_payee_expected_amount)
 ...
 ]
 uint8(data_string_size)
 size(data)
 * @param _payeesPaymentAddress array of payees payment addresses (the index 0 will be the payee the others are subPayees)
 * @param _expirationDate timestamp after that the signed request cannot be broadcasted
 * @param _signature ECDSA signature containing v, r and s as bytes
 *
 * @return Validity of order signature.
 */

function checkRequestSignature(
        bytes         _requestData,
        bytes         _payeesPaymentAddress,
        uint256       _expirationDate,
        bytes         _signature)
        public
        view
        returns (bool)
    {
        bytes32 hash = getRequestHash(_requestData, _payeesPaymentAddress, _expirationDate);

        // extract "v, r, s" from the signature
        uint8 v = uint8(_signature[64]);
        v = v < 27 ? v.add(27) : v;
        bytes32 r = extractBytes32(_signature, 0);
        bytes32 s = extractBytes32(_signature, 32);

        // check signature of the hash with the creator address
        return isValidSignature(extractAddress(_requestData, 0), hash, v, r, s);
    }


/*
 * @dev modify 20 bytes in a bytes
 * @param data bytes to modify
 * @param offset position of the first byte to modify
 * @param b bytes20 to insert
 * @return address
 */

function updateBytes20inBytes(bytes data, uint offset, bytes20 b)
        internal
        pure
    {
        require(offset >=0 && offset + 20 <= data.length);
        assembly {
            let m := mload(add(data, add(20, offset)))
            m := and(m, 0xFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000)
            m := or(m, div(b, 0x1000000000000000000000000))
            mstore(add(data, add(20, offset)), m)
        }
    }


/***************************************************************************
 * the DyDx will call `callFunction(address sender, Info memory accountInfo,
 * bytes memory data) public` after during `operate` call
 ***************************************************************************/

function flashloan(address token, uint256 amount, bytes memory data)
        internal
    {
        ERC20(token).approve(address(kDyDxPool), amount + 1);
        Info[] memory infos = new Info[](1);
        ActionArgs[] memory args = new ActionArgs[](3);

        infos[0] = Info(address(this), 0);

        AssetAmount memory wamt = AssetAmount(
            false,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount
        );

        ActionArgs memory withdraw;
        withdraw.actionType = ActionType.Withdraw;
        withdraw.accountId = 0;
        withdraw.amount = wamt;
        withdraw.primaryMarketId = tokenToMarketId(token);
        withdraw.otherAddress = address(this);

        args[0] = withdraw;

        ActionArgs memory call;
        call.actionType = ActionType.Call;
        call.accountId = 0;
        call.otherAddress = address(this);
        call.data = data;

        args[1] = call;

        ActionArgs memory deposit;
        AssetAmount memory damt = AssetAmount(
            true,
            AssetDenomination.Wei,
            AssetReference.Delta,
            amount + 1
        );
        deposit.actionType = ActionType.Deposit;
        deposit.accountId = 0;
        deposit.amount = damt;
        deposit.primaryMarketId = tokenToMarketId(token);
        deposit.otherAddress = address(this);

        args[2] = deposit;

        kDyDxPool.operate(infos, args);
    }


/*************************************************************************************************************
 * Call this contract function from the external
 * remote job to perform the liquidation.
 *
 ************************************************************************************************************/

function doCompFiLiquidate(
        //loan information
        address flashToken,
        uint256 flashAmount,
        // Borrow Account to be liquidated
        address targetAccount,
        address targetToken,
        uint256 liquidateAmount,
        // liquidation reimbursement and Reward Token
        address collateralToken
        ) external returns(bool) {

        emit PassThru( liquidateAmount );
        // Populate the passthru data structure, which will be used
        // by 'callFunction'
        bytes memory data = abi.encode(
            flashToken,
            flashAmount,
            targetAccount,
            targetToken,
            liquidateAmount,
            collateralToken);

        // execution goes to `callFunction`
        // STEP 1
        flashloan(flashToken, flashAmount, data);
        emit Liquidated( targetAccount, targetToken, liquidateAmount );
        return true;
    }


// Before calling setup, the sender must call Approve() on the AOC token
// That sets allowance for this contract to sell the tokens on sender's behalf

function setup(uint256 AOC_amount, uint256 price_in_wei) public {
        require(is_empty()); // must not be in cooldown
        require(AOC.allowance(msg.sender, this) >= AOC_amount); // contract needs enough allowance
        require(price_in_wei > 1000); // to avoid mistakes, require price to be more than 1000 wei

        price = price_in_wei;
        AOC_available = AOC_amount;
        Amount_of_AOC_for_One_ETH = 1 ether / price_in_wei;
        seller = msg.sender;

        require(AOC.transferFrom(msg.sender, this, AOC_amount)); // move AOC to this contract to hold in escrow
    }


//@dev Refund ether back to the investor in returns of proportional amount of SRN
//back to the Sirin`s wallet

function refundETH(uint256 ETHToRefundAmountWei) isInRefundTimeFrame isRefundingState public {
        require(ETHToRefundAmountWei != 0);

        uint256 depositedTokenValue = depositedToken[msg.sender];
        uint256 depositedETHValue = depositedETH[msg.sender];

        require(ETHToRefundAmountWei <= depositedETHValue);

        uint256 refundTokens = ETHToRefundAmountWei.mul(depositedTokenValue).div(depositedETHValue);

        assert(refundTokens > 0);

        depositedETH[msg.sender] = depositedETHValue.sub(ETHToRefundAmountWei);
        depositedToken[msg.sender] = depositedTokenValue.sub(refundTokens);

        token.destroy(address(this),refundTokens);
        msg.sender.transfer(ETHToRefundAmountWei);

        RefundedETH(msg.sender, ETHToRefundAmountWei);
    }


//@dev Transfer tokens from the vault to the investor while releasing proportional amount of ether
//to Sirin`s wallet.
//Can be triggerd by the investor only

function claimTokens(uint256 tokensToClaim) isRefundingOrCloseState public {
        require(tokensToClaim != 0);

        address investor = msg.sender;
        require(depositedToken[investor] > 0);

        uint256 depositedTokenValue = depositedToken[investor];
        uint256 depositedETHValue = depositedETH[investor];

        require(tokensToClaim <= depositedTokenValue);

        uint256 claimedETH = tokensToClaim.mul(depositedETHValue).div(depositedTokenValue);

        assert(claimedETH > 0);

        depositedETH[investor] = depositedETHValue.sub(claimedETH);
        depositedToken[investor] = depositedTokenValue.sub(tokensToClaim);

        token.transfer(investor, tokensToClaim);
        if(state != State.Closed) {
            etherWallet.transfer(claimedETH);
        }

        TokensClaimed(investor, tokensToClaim);
    }


// =================================================================================================================
//                                      Constructors
// =================================================================================================================

function SirinCrowdsale(uint256 _startTime,
    uint256 _endTime,
    address _wallet,
    address _walletTeam,
    address _walletOEM,
    address _walletBounties,
    address _walletReserve,
    SirinSmartToken _sirinSmartToken,
    RefundVault _refundVault)
    public
    Crowdsale(_startTime, _endTime, EXCHANGE_RATE, _wallet, _sirinSmartToken) {
        require(_walletTeam != address(0));
        require(_walletOEM != address(0));
        require(_walletBounties != address(0));
        require(_walletReserve != address(0));
        require(_sirinSmartToken != address(0));
        require(_refundVault != address(0));

        walletTeam = _walletTeam;
        walletOEM = _walletOEM;
        walletBounties = _walletBounties;
        walletReserve = _walletReserve;

        token = _sirinSmartToken;
        refundVault  = _refundVault;
    }


// =================================================================================================================
//                                      Impl Crowdsale
// =================================================================================================================
// @return the rate in SRN per 1 ETH according to the time of the tx and the SRN pricing program.
// @Override

function getRate() public view returns (uint256) {
        if (now < (startTime.add(24 hours))) {return 1000;}
        if (now < (startTime.add(2 days))) {return 950;}
        if (now < (startTime.add(3 days))) {return 900;}
        if (now < (startTime.add(4 days))) {return 855;}
        if (now < (startTime.add(5 days))) {return 810;}
        if (now < (startTime.add(6 days))) {return 770;}
        if (now < (startTime.add(7 days))) {return 730;}
        if (now < (startTime.add(8 days))) {return 690;}
        if (now < (startTime.add(9 days))) {return 650;}
        if (now < (startTime.add(10 days))) {return 615;}
        if (now < (startTime.add(11 days))) {return 580;}
        if (now < (startTime.add(12 days))) {return 550;}
        if (now < (startTime.add(13 days))) {return 525;}

        return rate;
    }


// =================================================================================================================
//                                      External Methods
// =================================================================================================================
// @dev Adds/Updates address and token allocation for token grants.
// Granted tokens are allocated to non-ether, presale, buyers.
// @param _grantee address The address of the token grantee.
// @param _value uint256 The value of the grant in wei token.

function addUpdateGrantee(address _grantee, uint256 _value) external onlyOwner onlyWhileSale{
        require(_grantee != address(0));
        require(_value > 0);

        // Adding new key if not present:
        if (presaleGranteesMap[_grantee] == 0) {
            require(presaleGranteesMapKeys.length < MAX_TOKEN_GRANTEES);
            presaleGranteesMapKeys.push(_grantee);
            GrantAdded(_grantee, _value);
        }
        else {
            GrantUpdated(_grantee, presaleGranteesMap[_grantee], _value);
        }

        presaleGranteesMap[_grantee] = _value;
    }


// @dev deletes entries from the grants list.
// @param _grantee address The address of the token grantee.

function deleteGrantee(address _grantee) external onlyOwner onlyWhileSale {
        require(_grantee != address(0));
        require(presaleGranteesMap[_grantee] != 0);

        //delete from the map:
        delete presaleGranteesMap[_grantee];

        //delete from the array (keys):
        uint256 index;
        for (uint256 i = 0; i < presaleGranteesMapKeys.length; i++) {
            if (presaleGranteesMapKeys[i] == _grantee) {
                index = i;
                break;
            }
        }
        presaleGranteesMapKeys[index] = presaleGranteesMapKeys[presaleGranteesMapKeys.length - 1];
        delete presaleGranteesMapKeys[presaleGranteesMapKeys.length - 1];
        presaleGranteesMapKeys.length--;

        GrantDeleted(_grantee, presaleGranteesMap[_grantee]);
    }


// @dev Buy tokes with guarantee

function buyTokensWithGuarantee() public payable {
        require(validPurchase());

        uint256 weiAmount = msg.value;

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

        // update state
        weiRaised = weiRaised.add(weiAmount);

        token.issue(address(refundVault), tokens);

        refundVault.deposit.value(msg.value)(msg.sender, tokens);

        TokenPurchaseWithGuarantee(msg.sender, address(refundVault), weiAmount, tokens);
    }


/**
 * @dev Recover signer address from a message by using his signature
 * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
 * @param sig bytes signature, the signature is generated using web3.eth.sign()
 */

function recover(bytes32 hash, bytes sig) public pure returns (address) {
    bytes32 r;
    bytes32 s;
    uint8 v;

    //Check the signature length
    if (sig.length != 65) {
      return (address(0));
    }

    // Divide the signature in r, s and v variables
    assembly {
      r := mload(add(sig, 32))
      s := mload(add(sig, 64))
      v := byte(0, mload(add(sig, 96)))
    }

    // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
    if (v < 27) {
      v += 27;
    }

    // If the version is correct return the signer address
    if (v != 27 && v != 28) {
      return (address(0));
    } else {
      return ecrecover(hash, v, r, s);
    }
  }


/**
 * If the position does not exist, create a new Position and add to the SetToken. If it already exists,
 * then set the position units. If the new units is 0, remove the position. Handles adding/removing of
 * components where needed (in light of potential external positions).
 *
 * @param _setToken           Address of SetToken being modified
 * @param _component          Address of the component
 * @param _newUnit            Quantity of Position units - must be >= 0
 */

function editDefaultPosition(ISetToken _setToken, address _component, uint256 _newUnit) internal {
        bool isPositionFound = hasDefaultPosition(_setToken, _component);
        if (!isPositionFound && _newUnit > 0) {
            // If there is no Default Position and no External Modules, then component does not exist
            if (!hasExternalPosition(_setToken, _component)) {
                _setToken.addComponent(_component);
            }
        } else if (isPositionFound && _newUnit == 0) {
            // If there is a Default Position and no external positions, remove the component
            if (!hasExternalPosition(_setToken, _component)) {
                _setToken.removeComponent(_component);
            }
        }

        _setToken.editDefaultPositionUnit(_component, _newUnit.toInt256());
    }


/**
 * Update an external position and remove and external positions or components if necessary. The logic flows as follows:
 * 1) If component is not already added then add component and external position.
 * 2) If component is added but no existing external position using the passed module exists then add the external position.
 * 3) If the existing position is being added to then just update the unit
 * 4) If the position is being closed and no other external positions or default positions are associated with the component
 *    then untrack the component and remove external position.
 * 5) If the position is being closed and  other existing positions still exist for the component then just remove the
 *    external position.
 *
 * @param _setToken         SetToken being updated
 * @param _component        Component position being updated
 * @param _module           Module external position is associated with
 * @param _newUnit          Position units of new external position
 * @param _data             Arbitrary data associated with the position
 */

function editExternalPosition(
        ISetToken _setToken,
        address _component,
        address _module,
        int256 _newUnit,
        bytes memory _data
    )
        internal
    {
        if (!_setToken.isComponent(_component)) {
            _setToken.addComponent(_component);
            addExternalPosition(_setToken, _component, _module, _newUnit, _data);
        } else if (!_setToken.isExternalPositionModule(_component, _module)) {
            addExternalPosition(_setToken, _component, _module, _newUnit, _data);
        } else if (_newUnit != 0) {
            _setToken.editExternalPositionUnit(_component, _module, _newUnit);
        } else {
            // If no default or external position remaining then remove component from components array
            if (_setToken.getDefaultPositionRealUnit(_component) == 0 && _setToken.getExternalPositionModules(_component).length == 1) {
                _setToken.removeComponent(_component);
            }
            _setToken.removeExternalPositionModule(_component, _module);
        }
    }


/**
 * Calculate the new position unit given total notional values pre and post executing an action that changes SetToken state
 * The intention is to make updates to the units without accidentally picking up airdropped assets as well.
 *
 * @param _setTokenSupply     Supply of SetToken in precise units (10^18)
 * @param _preTotalNotional   Total notional amount of component prior to executing action
 * @param _postTotalNotional  Total notional amount of component after the executing action
 * @param _prePositionUnit    Position unit of SetToken prior to executing action
 * @return                    New position unit
 */

function calculateDefaultEditPositionUnit(
        uint256 _setTokenSupply,
        uint256 _preTotalNotional,
        uint256 _postTotalNotional,
        uint256 _prePositionUnit
    )
        internal
        pure
        returns (uint256)
    {
        // If pre action total notional amount is greater then subtract post action total notional and calculate new position units
        if (_preTotalNotional >= _postTotalNotional) {
            uint256 unitsToSub = _preTotalNotional.sub(_postTotalNotional).preciseDivCeil(_setTokenSupply);
            return _prePositionUnit.sub(unitsToSub);
        } else {
            // Else subtract post action total notional from pre action total notional and calculate new position units
            uint256 unitsToAdd = _postTotalNotional.sub(_preTotalNotional).preciseDiv(_setTokenSupply);
            return _prePositionUnit.add(unitsToAdd);
        }
    }


/**
 * Finds the index of the first occurrence of the given element.
 * @param A The input array to search
 * @param a The value to find
 * @return Returns (index and isIn) for the first occurrence starting from index 0
 */

function indexOf(address[] memory A, address a) internal pure returns (uint256, bool) {
        uint256 length = A.length;
        for (uint256 i = 0; i < length; i++) {
            if (A[i] == a) {
                return (i, true);
            }
        }
        return (uint256(-1), false);
    }


/**
 * Returns true if there are 2 elements that are the same in an array
 * @param A The input array to search
 * @return Returns boolean for the first occurrence of a duplicate
 */

function hasDuplicate(address[] memory A) internal pure returns(bool) {
        require(A.length > 0, "A is empty");

        for (uint256 i = 0; i < A.length - 1; i++) {
            address current = A[i];
            for (uint256 j = i + 1; j < A.length; j++) {
                if (current == A[j]) {
                    return true;
                }
            }
        }
        return false;
    }


/**
 * @param A The input array to search
 * @param a The address to remove
 * @return Returns the array with the object removed.
 */

function remove(address[] memory A, address a)
        internal
        pure
        returns (address[] memory)
    {
        (uint256 index, bool isIn) = indexOf(A, a);
        if (!isIn) {
            revert("Address not in array.");
        } else {
            (address[] memory _A,) = pop(A, index);
            return _A;
        }
    }


/**
 * Removes specified index from array
 * @param A The input array to search
 * @param index The index to remove
 * @return Returns the new array and the removed entry
 */

function pop(address[] memory A, uint256 index)
        internal
        pure
        returns (address[] memory, address)
    {
        uint256 length = A.length;
        require(index < A.length, "Index must be < A length");
        address[] memory newAddresses = new address[](length - 1);
        for (uint256 i = 0; i < index; i++) {
            newAddresses[i] = A[i];
        }
        for (uint256 j = index + 1; j < length; j++) {
            newAddresses[j - 1] = A[j];
        }
        return (newAddresses, A[index]);
    }


/**
 * PRIVELEGED MODULE FUNCTION. Low level function that edits a component's virtual unit. Takes a real unit
 * and converts it to virtual before committing.
 */

function editDefaultPositionUnit(address _component, int256 _realUnit) external onlyModule whenLockedOnlyLocker {
        int256 virtualUnit = _convertRealToVirtualUnit(_realUnit);

        // These checks ensure that the virtual unit does not return a result that has rounded down to 0
        if (_realUnit > 0 && virtualUnit == 0) {
            revert("Virtual unit conversion invalid");
        }

        componentPositions[_component].virtualUnit = virtualUnit;

        emit DefaultPositionUnitEdited(_component, _realUnit);
    }


/**
 * Returns a list of Positions, through traversing the components. Each component with a non-zero virtual unit
 * is considered a Default Position, and each externalPositionModule will generate a unique position.
 * Virtual units are converted to real units. This function is typically used off-chain for data presentation purposes.
 */

function getPositions() external view returns (ISetToken.Position[] memory) {
        ISetToken.Position[] memory positions = new ISetToken.Position[](_getPositionCount());
        uint256 positionCount = 0;

        for (uint256 i = 0; i < components.length; i++) {
            address component = components[i];

            // A default position exists if the default virtual unit is > 0
            if (_defaultPositionVirtualUnit(component) > 0) {
                positions[positionCount] = ISetToken.Position({
                    component: component,
                    module: address(0),
                    unit: getDefaultPositionRealUnit(component),
                    positionState: DEFAULT,
                    data: ""
                });

                positionCount++;
            }

            address[] memory externalModules = _externalPositionModules(component);
            for (uint256 j = 0; j < externalModules.length; j++) {
                address currentModule = externalModules[j];

                positions[positionCount] = ISetToken.Position({
                    component: component,
                    module: currentModule,
                    unit: getExternalPositionRealUnit(component, currentModule),
                    positionState: EXTERNAL,
                    data: _externalPositionData(component, currentModule)
                });

                positionCount++;
            }
        }

        return positions;
    }


/**
 * Returns the total Real Units for a given component, summing the default and external position units.
 */

function getTotalComponentRealUnits(address _component) external view returns(int256) {
        int256 totalUnits = getDefaultPositionRealUnit(_component);

		address[] memory externalModules = _externalPositionModules(_component);
		for (uint256 i = 0; i < externalModules.length; i++) {
            // We will perform the summation no matter what, as an external position virtual unit can be negative
			totalUnits = totalUnits.add(getExternalPositionRealUnit(_component, externalModules[i]));
		}

		return totalUnits;
    }


/**
 * Gets the total number of positions, defined as the following:
 * - Each component has a default position if its virtual unit is > 0
 * - Each component's external positions module is counted as a position
 */

function _getPositionCount() internal view returns (uint256) {
        uint256 positionCount;
        for (uint256 i = 0; i < components.length; i++) {
            address component = components[i];

            // Increment the position count if the default position is > 0
            if (_defaultPositionVirtualUnit(component) > 0) {
                positionCount++;
            }

            // Increment the position count by each external position module
            address[] memory externalModules = _externalPositionModules(component);
            if (externalModules.length > 0) {
            	positionCount = positionCount.add(externalModules.length);
            }
        }

        return positionCount;
    }


/**
 *   @dev Allows to sign a transaction
 */

function signTransaction(uint _txId) public  onlySigners {
        require(!transactions[_txId].confirmed[msg.sender] && _txId <= txCount, "must be a valid unsigned tx");
        transactions[_txId].confirmed[msg.sender] = true;
        transactions[_txId].confirmations++;
        emit TranscationSigned(_txId, now, msg.sender);
        if (transactions[_txId].confirmations >= requiredConfirmations) {
            _sendTransaction(_txId);
      }
    }


//executing tx

function _sendTransaction(uint _txId) private {
        require(!transactions[_txId].done, "transaction must not be done");
        transactions[_txId].done = true;
        if ( transactions[_txId].tokenAddress == address(0)) {
            transactions[_txId].to.transfer(transactions[_txId].amount);
        } else {
            ERC20 token = ERC20(transactions[_txId].tokenAddress);
            require(token.transfer(transactions[_txId].to, transactions[_txId].amount), "token transfer failded");
        }
        emit TranscationSended(_txId, now);
    }


//mint LuckyBunnyClub

function mintLuckyBunnyClub(address _to, uint256 _count) external payable {
        require(
            totalSupply() + _count <= MAX_LBC,
            "Exceeds maximum supply of Lucky Bunny Club"
        );
         require(
            totalSupply() + _count <= maxAllow,
            "Exceeds maximum supply of this batch"
        );
        require(
            _count > 0,
            "Minimum 1 Lucky Bunny Club has to be minted per transaction"
        );
        if (msg.sender != owner()) {
            require(saleOpen, "Sale is not open yet");
            require(
                _count <= 20,
                "Maximum 20 Lucky Bunny Club can be minted per transaction"
            );
            require(
                msg.value >= price * _count,
                "Ether sent with this transaction is not correct"
            );
        }

        for (uint256 i = 0; i < _count; i++) {
            _mint(_to);
        }
    }


/**
 * Mints Crypto DRMS Token Assets
 */

function mintToken(string memory metadataURI) public payable {
        require(totalSupply().add(1) <= MAX_TOKENS, "Purchase would exceed max supply of Tokens");
        require(tokenPrice.mul(1) <= msg.value, "Ether value sent is not correct");

        uint mintIndex = totalSupply();
        if (totalSupply() < MAX_TOKENS) {
            _safeMint(msg.sender, mintIndex);
            _setTokenURI(mintIndex, metadataURI);
        }
    }


//solium-disable-next-line

function rayPow(uint256 x, uint256 n) internal pure returns (uint256 z) {

        z = n % 2 != 0 ? x : RAY;

        for (n /= 2; n != 0; n /= 2) {
            x = rayMul(x, x);

            if (n % 2 != 0) {
                z = rayMul(z, x);
            }
        }
    }


/**
 * @dev internal function to update the implementation of a specific component of the protocol
 * @param _id the id of the contract to be updated
 * @param _newAddress the address of the new implementation
 **/

function updateImplInternal(bytes32 _id, address _newAddress) internal {
        address payable proxyAddress = address(uint160(getAddress(_id)));

        InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(proxyAddress);
        bytes memory params = abi.encodeWithSignature("initialize(address)", address(this));

        if (proxyAddress == address(0)) {
            proxy = new InitializableAdminUpgradeabilityProxy();
            proxy.initialize(_newAddress, address(this), params);
            _setAddress(_id, address(proxy));
            emit ProxyCreated(_id, address(proxy));
        } else {
            proxy.upgradeToAndCall(_newAddress, params);
        }

    }


/**
 * @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
 * a formal specification.
 * @param _self the reserve object
 **/

function updateCumulativeIndexes(ReserveData storage _self) internal {
        uint256 totalBorrows = getTotalBorrows(_self);

        if (totalBorrows > 0) {
            //only cumulating if there is any income being produced
            uint256 cumulatedLiquidityInterest = calculateLinearInterest(
                _self.currentLiquidityRate,
                _self.lastUpdateTimestamp
            );

            _self.lastLiquidityCumulativeIndex = cumulatedLiquidityInterest.rayMul(
                _self.lastLiquidityCumulativeIndex
            );

            uint256 cumulatedVariableBorrowInterest = calculateCompoundedInterest(
                _self.currentVariableBorrowRate,
                _self.lastUpdateTimestamp
            );
            _self.lastVariableBorrowCumulativeIndex = cumulatedVariableBorrowInterest.rayMul(
                _self.lastVariableBorrowCumulativeIndex
            );
        }
    }


/**
 * @dev initializes a reserve
 * @param _self the reserve object
 * @param _aTokenAddress the address of the overlying atoken contract
 * @param _decimals the number of decimals of the underlying asset
 * @param _interestRateStrategyAddress the address of the interest rate strategy contract
 **/

function init(
        ReserveData storage _self,
        address _aTokenAddress,
        uint256 _decimals,
        address _interestRateStrategyAddress
    ) external {
        require(_self.aTokenAddress == address(0), "Reserve has already been initialized");

        if (_self.lastLiquidityCumulativeIndex == 0) {
            //if the reserve has not been initialized yet
            _self.lastLiquidityCumulativeIndex = WadRayMath.ray();
        }

        if (_self.lastVariableBorrowCumulativeIndex == 0) {
            _self.lastVariableBorrowCumulativeIndex = WadRayMath.ray();
        }

        _self.aTokenAddress = _aTokenAddress;
        _self.decimals = _decimals;

        _self.interestRateStrategyAddress = _interestRateStrategyAddress;
        _self.isActive = true;
        _self.isFreezed = false;

    }


/**
 * @dev enables a reserve to be used as collateral
 * @param _self the reserve object
 * @param _baseLTVasCollateral the loan to value of the asset when used as collateral
 * @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized
 * @param _liquidationBonus the bonus liquidators receive to liquidate this asset
 **/

function enableAsCollateral(
        ReserveData storage _self,
        uint256 _baseLTVasCollateral,
        uint256 _liquidationThreshold,
        uint256 _liquidationBonus
    ) external {
        require(
            _self.usageAsCollateralEnabled == false,
            "Reserve is already enabled as collateral"
        );

        _self.usageAsCollateralEnabled = true;
        _self.baseLTVasCollateral = _baseLTVasCollateral;
        _self.liquidationThreshold = _liquidationThreshold;
        _self.liquidationBonus = _liquidationBonus;

        if (_self.lastLiquidityCumulativeIndex == 0)
            _self.lastLiquidityCumulativeIndex = WadRayMath.ray();

    }


/**
 * @dev calculates the compounded borrow balance of a user
 * @param _self the userReserve object
 * @param _reserve the reserve object
 * @return the user compounded borrow balance
 **/

function getCompoundedBorrowBalance(
        CoreLibrary.UserReserveData storage _self,
        CoreLibrary.ReserveData storage _reserve
    ) internal view returns (uint256) {
        if (_self.principalBorrowBalance == 0) return 0;

        uint256 principalBorrowBalanceRay = _self.principalBorrowBalance.wadToRay();
        uint256 compoundedBalance = 0;
        uint256 cumulatedInterest = 0;

        if (_self.stableBorrowRate > 0) {
            cumulatedInterest = calculateCompoundedInterest(
                _self.stableBorrowRate,
                _self.lastUpdateTimestamp
            );
        } else {
            //variable interest
            cumulatedInterest = calculateCompoundedInterest(
                _reserve
                    .currentVariableBorrowRate,
                _reserve
                    .lastUpdateTimestamp
            )
                .rayMul(_reserve.lastVariableBorrowCumulativeIndex)
                .rayDiv(_self.lastVariableBorrowCumulativeIndex);
        }

        compoundedBalance = principalBorrowBalanceRay.rayMul(cumulatedInterest).rayToWad();

        if (compoundedBalance == _self.principalBorrowBalance) {
            //solium-disable-next-line
            if (_self.lastUpdateTimestamp != block.timestamp) {
                //no interest cumulation because of the rounding - we add 1 wei
                //as symbolic cumulated interest to avoid interest free loans.

                return _self.principalBorrowBalance.add(1 wei);
            }
        }

        return compoundedBalance;
    }


/**
 * @dev increases the total borrows at a stable rate on a specific reserve and updates the
 * average stable rate consequently
 * @param _reserve the reserve object
 * @param _amount the amount to add to the total borrows stable
 * @param _rate the rate at which the amount has been borrowed
 **/

function increaseTotalBorrowsStableAndUpdateAverageRate(
        ReserveData storage _reserve,
        uint256 _amount,
        uint256 _rate
    ) internal {
        uint256 previousTotalBorrowStable = _reserve.totalBorrowsStable;
        //updating reserve borrows stable
        _reserve.totalBorrowsStable = _reserve.totalBorrowsStable.add(_amount);

        //update the average stable rate
        //weighted average of all the borrows
        uint256 weightedLastBorrow = _amount.wadToRay().rayMul(_rate);
        uint256 weightedPreviousTotalBorrows = previousTotalBorrowStable.wadToRay().rayMul(
            _reserve.currentAverageStableBorrowRate
        );

        _reserve.currentAverageStableBorrowRate = weightedLastBorrow
            .add(weightedPreviousTotalBorrows)
            .rayDiv(_reserve.totalBorrowsStable.wadToRay());
    }


/**
 * @dev check if a specific balance decrease is allowed (i.e. doesn't bring the user borrow position health factor under 1e18)
 * @param _reserve the address of the reserve
 * @param _user the address of the user
 * @param _amount the amount to decrease
 * @return true if the decrease of the balance is allowed
 **/

function balanceDecreaseAllowed(address _reserve, address _user, uint256 _amount)
        external
        view
        returns (bool)
    {
        // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
        balanceDecreaseAllowedLocalVars memory vars;

        (
            vars.decimals,
            ,
            vars.reserveLiquidationThreshold,
            vars.reserveUsageAsCollateralEnabled
        ) = core.getReserveConfiguration(_reserve);

        if (
            !vars.reserveUsageAsCollateralEnabled ||
            !core.isUserUseReserveAsCollateralEnabled(_reserve, _user)
        ) {
            return true; //if reserve is not used as collateral, no reasons to block the transfer
        }

        (
            ,
            vars.collateralBalanceETH,
            vars.borrowBalanceETH,
            vars.totalFeesETH,
            ,
            vars.currentLiquidationThreshold,
            ,

        ) = calculateUserGlobalData(_user);

        if (vars.borrowBalanceETH == 0) {
            return true; //no borrows - no reasons to block the transfer
        }

        IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());

        vars.amountToDecreaseETH = oracle.getAssetPrice(_reserve).mul(_amount).div(
            10 ** vars.decimals
        );

        vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub(
            vars.amountToDecreaseETH
        );

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

        vars.liquidationThresholdAfterDecrease = vars
            .collateralBalanceETH
            .mul(vars.currentLiquidationThreshold)
            .sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold))
            .div(vars.collateralBalancefterDecrease);

        uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalancesInternal(
            vars.collateralBalancefterDecrease,
            vars.borrowBalanceETH,
            vars.totalFeesETH,
            vars.liquidationThresholdAfterDecrease
        );

        return healthFactorAfterDecrease > HEALTH_FACTOR_LIQUIDATION_THRESHOLD;

    }


/**
 * @notice calculates the amount of collateral needed in ETH to cover a new borrow.
 * @param _reserve the reserve from which the user wants to borrow
 * @param _amount the amount the user wants to borrow
 * @param _fee the fee for the amount that the user needs to cover
 * @param _userCurrentBorrowBalanceTH the current borrow balance of the user (before the borrow)
 * @param _userCurrentLtv the average ltv of the user given his current collateral
 * @return the total amount of collateral in ETH to cover the current borrow balance + the new amount + fee
 **/

function calculateCollateralNeededInETH(
        address _reserve,
        uint256 _amount,
        uint256 _fee,
        uint256 _userCurrentBorrowBalanceTH,
        uint256 _userCurrentFeesETH,
        uint256 _userCurrentLtv
    ) external view returns (uint256) {
        uint256 reserveDecimals = core.getReserveDecimals(_reserve);

        IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());

        uint256 requestedBorrowAmountETH = oracle
            .getAssetPrice(_reserve)
            .mul(_amount.add(_fee))
            .div(10 ** reserveDecimals); //price is in ether

        //add the current already borrowed amount to the amount requested to calculate the total collateral needed.
        uint256 collateralNeededInETH = _userCurrentBorrowBalanceTH
            .add(_userCurrentFeesETH)
            .add(requestedBorrowAmountETH)
            .mul(100)
            .div(_userCurrentLtv); //LTV is calculated in percentage

        return collateralNeededInETH;

    }


/**
 * @dev accessory functions to fetch data from the lendingPoolCore
 **/

function getReserveConfigurationData(address _reserve)
        external
        view
        returns (
            uint256 ltv,
            uint256 liquidationThreshold,
            uint256 liquidationBonus,
            address rateStrategyAddress,
            bool usageAsCollateralEnabled,
            bool borrowingEnabled,
            bool stableBorrowRateEnabled,
            bool isActive
        )
    {
        (, ltv, liquidationThreshold, usageAsCollateralEnabled) = core.getReserveConfiguration(
            _reserve
        );
        stableBorrowRateEnabled = core.getReserveIsStableBorrowRateEnabled(_reserve);
        borrowingEnabled = core.isReserveBorrowingEnabled(_reserve);
        isActive = core.getReserveIsActive(_reserve);
        liquidationBonus = core.getReserveLiquidationBonus(_reserve);

        rateStrategyAddress = core.getReserveInterestRateStrategyAddress(_reserve);
    }


/**
 * @dev mints token in the event of users depositing the underlying asset into the lending pool
 * only lending pools can call this function
 * @param _account the address receiving the minted tokens
 * @param _amount the amount of tokens to mint
 */

function mintOnDeposit(address _account, uint256 _amount) external onlyLendingPool {

        //cumulates the balance of the user
        (,
        ,
        uint256 balanceIncrease,
        uint256 index) = cumulateBalanceInternal(_account);

         //if the user is redirecting his interest towards someone else,
        //we update the redirected balance of the redirection address by adding the accrued interest
        //and the amount deposited
        updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease.add(_amount), 0);

        //mint an equivalent amount of tokens to cover the new deposit
        _mint(_account, _amount);

        emit MintOnDeposit(_account, _amount, balanceIncrease, index);
    }


/**
 * @dev burns token in the event of a borrow being liquidated, in case the liquidators reclaims the underlying asset
 * Transfer of the liquidated asset is executed by the lending pool contract.
 * only lending pools can call this function
 * @param _account the address from which burn the aTokens
 * @param _value the amount to burn
 **/

function burnOnLiquidation(address _account, uint256 _value) external onlyLendingPool {

        //cumulates the balance of the user being liquidated
        (,uint256 accountBalance,uint256 balanceIncrease,uint256 index) = cumulateBalanceInternal(_account);

        //adds the accrued interest and substracts the burned amount to
        //the redirected balance
        updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease, _value);

        //burns the requested amount of tokens
        _burn(_account, _value);

        bool userIndexReset = false;
        //reset the user data if the remaining balance is 0
        if(accountBalance.sub(_value) == 0){
            userIndexReset = resetDataOnZeroBalanceInternal(_account);
        }

        emit BurnOnLiquidation(_account, _value, balanceIncrease, userIndexReset ? 0 : index);
    }


/**
 * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
 *      only lending pools can call this function
 * @param _from the address from which transfer the aTokens
 * @param _to the destination address
 * @param _value the amount to transfer
 **/

function transferOnLiquidation(address _from, address _to, uint256 _value) external onlyLendingPool {

        //being a normal transfer, the Transfer() and BalanceTransfer() are emitted
        //so no need to emit a specific event here
        executeTransferInternal(_from, _to, _value);
    }


/**
 * @dev calculates the balance of the user, which is the
 * principal balance + interest generated by the principal balance + interest generated by the redirected balance
 * @param _user the user for which the balance is being calculated
 * @return the total balance of the user
 **/

function balanceOf(address _user) public view returns(uint256) {

        //current principal balance of the user
        uint256 currentPrincipalBalance = super.balanceOf(_user);
        //balance redirected by other users to _user for interest rate accrual
        uint256 redirectedBalance = redirectedBalances[_user];

        if(currentPrincipalBalance == 0 && redirectedBalance == 0){
            return 0;
        }
        //if the _user is not redirecting the interest to anybody, accrues
        //the interest for himself

        if(interestRedirectionAddresses[_user] == address(0)){

            //accruing for himself means that both the principal balance and
            //the redirected balance partecipate in the interest
            return calculateCumulatedBalanceInternal(
                _user,
                currentPrincipalBalance.add(redirectedBalance)
                )
                .sub(redirectedBalance);
        }
        else {
            //if the user redirected the interest, then only the redirected
            //balance generates interest. In that case, the interest generated
            //by the redirected balance is added to the current principal balance.
            return currentPrincipalBalance.add(
                calculateCumulatedBalanceInternal(
                    _user,
                    redirectedBalance
                )
                .sub(redirectedBalance)
            );
        }
    }


/**
 * @dev accumulates the accrued interest of the user to the principal balance
 * @param _user the address of the user for which the interest is being accumulated
 * @return the previous principal balance, the new principal balance, the balance increase
 * and the new user index
 **/

function cumulateBalanceInternal(address _user)
        internal
        returns(uint256, uint256, uint256, uint256) {

        uint256 previousPrincipalBalance = super.balanceOf(_user);

        //calculate the accrued interest since the last accumulation
        uint256 balanceIncrease = balanceOf(_user).sub(previousPrincipalBalance);
        //mints an amount of tokens equivalent to the amount accumulated
        _mint(_user, balanceIncrease);
        //updates the user index
        uint256 index = userIndexes[_user] = core.getReserveNormalizedIncome(underlyingAssetAddress);
        return (
            previousPrincipalBalance,
            previousPrincipalBalance.add(balanceIncrease),
            balanceIncrease,
            index
        );
    }