/**
 * @dev creates new GLX tokens
 *      It is a internal function it will be called by fallback function or buyToken functions.
 */

function createTokens() internal  {
      if (emergencyFlag) revert();                     //  Revert when the sale is over before time and emergencyFlag is true.
      if (block.number > fundingEndBlock) revert();   //   If the blocknumber exceed the ending block it will revert
      if (msg.value<minTokenPurchaseAmount)revert();  //    If someone send 0.08 ether it will fail
      uint256 tokenExchangeRate=tokenRate();        //     It will get value depending upon block number and presale cap
      uint256 tokens = safeMult(msg.value, tokenExchangeRate);//  Calculating number of token for sender
      totalSupply = safeAdd(totalSupply, tokens);            //   Add token to total supply
      if(totalSupply>tokenCreationCap)revert();             //    Check the total supply if it is more then hardcap it will throw
      balances[msg.sender] += tokens;                      //     Adding token to sender account
      CreateGLX(msg.sender, tokens);                      //      Logs sender address and  token creation
    }


/**
 * @dev     it will  assign token to a particular address by owner only
 * @param   _to the address whom you want to send token to
 * @param   _amount the amount you want to send
 * @return  It will return true if success.
 */

function mint(address _to, uint256 _amount) external onlyOwner returns (bool) {
      if (emergencyFlag) revert();
      totalSupply = safeAdd(totalSupply,_amount);// Add the minted token to total suppy
      if(totalSupply>tokenCreationCap)revert();
      balances[_to] +=_amount;                 //   Adding token to the input address
      Mint(_to, _amount);                     //    Log the mint with address and token given to particular address
      return true;
    }


/**
 * @dev     it will change the ending date of ico and access by owner only
 * @param   _newBlock enter the future blocknumber
 * @return  It will return the blocknumber
 */

function changeEndBlock(uint256 _newBlock) external onlyOwner returns (uint256 _endblock )
    {   // we are expecting that owner will input number greater than current block.
        require(_newBlock > fundingStartBlock);
        fundingEndBlock = _newBlock;         // New block is assigned to extend the Crowd Sale time
        return fundingEndBlock;
    }


// Stake ID uniquely identifies a stake
// (note, `stakeNum` uniquely identifies a stake, rest is for UI sake)

function encodeStakeId(
        address token, // token contract address
        uint256 stakeNum, // uniq nonce (limited to 48 bits)
        uint256 unlockTime, // UNIX time (limited to 32 bits)
        uint256 stakeHours // Stake duration (limited to 16 bits)
    ) public pure returns (uint256) {
        require(stakeNum < 2**48, "QDeck:stakeNum_EXCEEDS_48_BITS");
        require(unlockTime < 2**32, "QDeck:unlockTime_EXCEEDS_32_BITS");
        require(stakeHours < 2**16, "QDeck:stakeHours_EXCEEDS_16_BITS");
        return _encodeStakeId(token, stakeNum, unlockTime, stakeHours);
    }


// Returns `true` if the term sheet has NOT been yet added.

function _isMissingTerms(TermSheet memory newSheet)
        internal
        view
        returns (bool)
    {
        for (uint256 i = 0; i < termSheets.length; i++) {
            TermSheet memory sheet = termSheets[i];
            if (
                sheet.token == newSheet.token &&
                sheet.minAmount == newSheet.minAmount &&
                sheet.maxAmountFactor == newSheet.maxAmountFactor &&
                sheet.lockHours == newSheet.lockHours &&
                sheet.rewardLockHours == newSheet.rewardLockHours &&
                sheet.rewardFactor == newSheet.rewardFactor
            ) {
                return false;
            }
        }
        return true;
    }


// Assuming the given array does contain the given element

function _removeArrayElement(uint256[] storage arr, uint256 el) internal {
        uint256 lastIndex = arr.length - 1;
        if (lastIndex != 0) {
            uint256 replaced = arr[lastIndex];
            if (replaced != el) {
                // Shift elements until the one being removed is replaced
                do {
                    uint256 replacing = replaced;
                    replaced = arr[lastIndex - 1];
                    lastIndex--;
                    arr[lastIndex] = replacing;
                } while (replaced != el && lastIndex != 0);
            }
        }
        // Remove the last (and quite probably the only) element
        arr.pop();
    }


/// @dev Creates a new Artwork and mint an edition

function createArtworkAndMintEdition(
        uint256 _artworkNumber,
        uint8 _scarcity,
        string memory _tokenUri,
        uint16 _totalLeft,
        bool _active,
        address _to
    ) external whenNotPaused onlyMinter returns (uint256) {
        _createArtwork(
            _artworkNumber,
            _scarcity,
            _tokenUri,
            _totalLeft,
            _active
        );

        // Create the token
        return
            _mintEdition(
                _to,
                _artworkNumber,
                artworkNumberToArtworkDetails[_artworkNumber].tokenUri
            );
    }


/// @dev Get artwork details from artworkNumber

function getArtwork(uint256 artworkNumber)
        external
        view
        returns (
            uint8 scarcity,
            uint16 totalMinted,
            uint16 totalLeft,
            string memory tokenUri,
            bool active
        )
    {
        ArtworkDetails storage a = artworkNumberToArtworkDetails[artworkNumber];
        scarcity = a.scarcity;
        totalMinted = a.totalMinted;
        totalLeft = a.totalLeft;
        tokenUri = a.tokenUri;
        active = a.active;
    }


/**
 * @dev Internal factory method to generate an new tokenId based
 * @dev based on artwork number
 */

function _getNextTokenId(uint256 _artworkNumber)
        internal
        view
        returns (uint256)
    {
        ArtworkDetails storage _artworkDetails = artworkNumberToArtworkDetails[
            _artworkNumber
        ];

        // Build next token ID e.g. 100000 + (1 - 1) = ID of 100001 (this first in the edition set)
        return _artworkDetails.artworkNumber.add(_artworkDetails.totalMinted);
    }


/**
 * @dev Internal factory method to mint a new edition
 * @dev for an artwork
 */

function _mintEdition(
        address _to,
        uint256 _artworkNumber,
        string memory _tokenUri
    ) internal returns (uint256) {
        // Construct next token ID e.g. 100000 + 1 = ID of 100001 (this first in the edition set)
        uint256 _tokenId = _getNextTokenId(_artworkNumber);

        // Mint new base token
        super._mint(_to, _tokenId);
        super._setTokenURI(_tokenId, _tokenUri);

        // Maintain mapping for tokenId to artwork for lookup
        tokenIdToArtworkNumber[_tokenId] = _artworkNumber;

        // Maintain mapping of edition to token array for "edition minted tokens"
        artworkNumberToTokenIds[_artworkNumber].push(_tokenId);

        ArtworkDetails storage _artworkDetails = artworkNumberToArtworkDetails[
            _artworkNumber
        ];
        // Increase number totalMinted
        _artworkDetails.totalMinted += 1;
        // Decrease number totalLeft
        _artworkDetails.totalLeft -= 1;

        // Emit minted event
        emit ArtworkEditionMinted(_tokenId, _artworkNumber, _to);
        return _tokenId;
    }


/// @dev Migrates tokens to a potential new version of this contract
/// @param tokenIds - list of tokens to transfer

function migrateTokens(uint256[] calldata tokenIds) external {
        require(
            address(newContract) != address(0),
            "LaCollection: New contract not set"
        );

        for (uint256 index = 0; index < tokenIds.length; index++) {
            transferFrom(_msgSender(), address(this), tokenIds[index]);
        }

        newContract.migrateTokens(tokenIds, _msgSender());
    }


//Yes this is ugly but it saves gas through binary search & probability theory, a total of 5-10 ETH for our dear hodlers will be saved because of this ugliness

function multiMint(uint amount, address to) private {
        require(amount > 0, "Invalid amount");
        require(_checkOnERC721Received(address(0), to, _mint(to), ''), "ERC721: transfer to non ERC721Receiver implementer"); //Safe mint 1st and regular mint rest to save gas!
        if (amount < 4) {
            if (amount == 2) {
                _mint(to);
            } else if (amount == 3) {
                _mint(to);
                _mint(to);
            }
        } else {
            if (amount > 5) {
                if (amount == 6) {
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                } else { // 7
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                }
            } else {
                if (amount == 4) {
                    _mint(to);
                    _mint(to);
                    _mint(to);
                } else { // 5
                    _mint(to);
                    _mint(to);
                    _mint(to);
                    _mint(to);
                }
            }
        }

    }


/*
 * @dev Get GMI to user
 */

function getUnLockedGMI() public
	isActivated()
	isExhausted()
		payable {

 		uint256 currentTakeGMI = 0;
 		uint256 unlockedCount = 0;
		uint256 unlockedGMI = 0;
		uint256 userLockedGMI = 0;
		uint256 userTakeTime = 0;
        (currentTakeGMI, unlockedCount, unlockedGMI, userLockedGMI, userTakeTime) = calculateUnLockerGMI(msg.sender);
		takenAmount[msg.sender] = safeAdd(takenAmount[msg.sender], currentTakeGMI);
		takenTime[msg.sender] = now;
		gmiToken.transfer(msg.sender, currentTakeGMI);
    }


/*
 * @dev  calculate user unlocked GMI amount
 */

function calculateUnLockerGMI(address userAddr) private isActivated()
    view returns(uint256, uint256, uint256, uint256, uint256)  {
        uint256 unlockedCount = 0;
		uint256 currentTakeGMI = 0;
		uint256 userTakenTime = takenTime[userAddr];
		uint256 userLockedGMI = lockList[userAddr];

	    unlockedCount = safeDiv(safeSub(now, activatedTime), timeInterval);

		if(unlockedCount == 0) {
		    return (0, unlockedCount, unlockedGMI, userLockedGMI, userTakenTime);
		}

		if(unlockedCount > unLockedAmount) {
		    unlockedCount = unLockedAmount;
		}

		uint256 unlockedGMI =  safeDiv(safeMul(userLockedGMI, unlockedCount), unLockedAmount);
		currentTakeGMI = safeSub(unlockedGMI, takenAmount[userAddr]);
		if(unlockedCount == unLockedAmount) {
		   currentTakeGMI = safeSub(userLockedGMI, takenAmount[userAddr]);
		}
	    return (currentTakeGMI, unlockedCount, unlockedGMI, userLockedGMI, userTakenTime);
    }


/**
 * @dev Limit burn functions to owner and reduce cap after burning
 */

function _burn(address _who, uint256 _value) internal onlyOwner {
        // no need to check _value <= cap since totalSupply <= cap and
        // _value <= totalSupply was checked in burn functions
        super._burn(_who, _value);
        cap = cap.sub(_value);
    }


/**
 * Creates new tokens. Can only be called by one of the three owners. Includes
 * signatures from each of the 3 owners.
 * The signed messages is a bytes32(equivalent to uint256), which includes the
 * nonce and the amount intended to be minted. The network ID is not included,
 * which means owner keys cannot be shared across networks because of the
 * possibility of replay. The lower 128 bits of the signedMessage contain
 * the amount to be minted, and the upper 128 bits contain the nonce.
 */

function mint(bytes32 signedMessage, uint8 sigV1, bytes32 sigR1, bytes32 sigS1, uint8 sigV2, bytes32 sigR2, bytes32 sigS2, uint8 sigV3, bytes32 sigR3, bytes32 sigS3) external {
    require(isOwner(), "Must be owner");
    require(ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", signedMessage)), sigV1, sigR1, sigS1) == owner1, "Not approved by owner1");
    require(ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", signedMessage)), sigV2, sigR2, sigS2) == owner2, "Not approved by owner2");
    require(ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", signedMessage)), sigV3, sigR3, sigS3) == owner3, "Not approved by owner3");
    // cast message to a uint256
    uint256 signedMessageUint256 = uint256(signedMessage);
    // bitwise-and the lower 128 bits of message to get the amount
    uint256 amount = signedMessageUint256 & (2**128-1);
    // right-shift the message by 128 bits to get the nonce in the correct position
    signedMessageUint256 = signedMessageUint256 / (2**128);
    // bitwise-and the message by 128 bits to get the nonce
    uint32 nonce = uint32(signedMessageUint256 & (2**128-1));
    require(nonce == mintingNonce, "nonce must match");
    mintingNonce += 1;
    _mint(owner1, amount);
    emit Minted(owner1, amount);
  }


/**
 * @dev See {IRoyaltyEngineV1-getRoyalty}
 */

function getRoyalty(address tokenAddress, uint256 tokenId, uint256 value) public override returns(address payable[] memory recipients, uint256[] memory amounts) {
        int16 spec;
        address royaltyAddress;
        bool addToCache;

        (recipients, amounts, spec, royaltyAddress, addToCache) = _getRoyaltyAndSpec(tokenAddress, tokenId, value);
        if (addToCache) _specCache[royaltyAddress] = spec;
        return (recipients, amounts);
    }


/**
 * @dev See {IRoyaltyEngineV1-getRoyaltyView}.
 */

function getRoyaltyView(address tokenAddress, uint256 tokenId, uint256 value) public view override returns(address payable[] memory recipients, uint256[] memory amounts) {
        (recipients, amounts, , , ) = _getRoyaltyAndSpec(tokenAddress, tokenId, value);
        return (recipients, amounts);
    }


/**
 * Compute royalty amounts
 */

function _computeAmounts(uint256 value, uint256[] memory bps) private pure returns(uint256[] memory amounts) {
        amounts = new uint256[](bps.length);
        uint256 totalAmount;
        for (uint i = 0; i < bps.length; i++) {
            amounts[i] = value*bps[i]/10000;
            totalAmount += amounts[i];
        }
        require(totalAmount < value, "Invalid royalty amount");
        return amounts;
    }


/// @notice Fetches a time-weighted observation for a given Uniswap V3 pool
/// @param pool Address of the pool that we want to observe
/// @param period Number of seconds in the past to start calculating the time-weighted observation
/// @return observation An observation that has been time-weighted from (block.timestamp - period) to block.timestamp

function consult(address pool, uint32 period) internal view returns (PeriodObservation memory observation) {
    require(period != 0, 'BP');

    uint192 periodX160 = uint192(period) * type(uint160).max;

    uint32[] memory secondsAgos = new uint32[](2);
    secondsAgos[0] = period;
    secondsAgos[1] = 0;

    (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = IUniswapV3Pool(pool).observe(secondsAgos);
    int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0];
    uint160 secondsPerLiquidityCumulativesDelta = secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0];

    observation.arithmeticMeanTick = int24(tickCumulativesDelta / period);
    // Always round to negative infinity
    if (tickCumulativesDelta < 0 && (tickCumulativesDelta % period != 0)) observation.arithmeticMeanTick--;

    // We are shifting the liquidity delta to ensure that the result doesn't overflow uint128
    observation.harmonicMeanLiquidity = uint128(periodX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32));
  }


/// @notice Given some time-weighted observations, calculates the arithmetic mean tick, weighted by liquidity
/// @param observations A list of time-weighted observations
/// @return arithmeticMeanWeightedTick The arithmetic mean tick, weighted by the observations' time-weighted harmonic average liquidity

function getArithmeticMeanTickWeightedByLiquidity(PeriodObservation[] memory observations)
    internal
    pure
    returns (int24 arithmeticMeanWeightedTick)
  {
    // Accumulates the sum of all observations' products between each their own average tick and harmonic average liquidity
    // Each product can be stored in a int160, so it would take approximatelly 2**96 observations to overflow this accumulator
    int256 numerator;

    // Accumulates the sum of the harmonic average liquidities from the given observations
    // Each average liquidity can be stored in a uint128, so it will take approximatelly 2**128 observations to overflow this accumulator
    uint256 denominator;

    for (uint256 i; i < observations.length; i++) {
      numerator += int256(observations[i].harmonicMeanLiquidity) * observations[i].arithmeticMeanTick;
      denominator += observations[i].harmonicMeanLiquidity;
    }

    arithmeticMeanWeightedTick = int24(numerator / int256(denominator));

    // Always round to negative infinity
    if (numerator < 0 && (numerator % int256(denominator) != 0)) arithmeticMeanWeightedTick--;
  }


/**
 * @dev Function to check the amount of tokens that an owner allowed to a spender.
 * @param _totalSupply total supply of the token.
 * @param _name token name e.g Dijets.
 * @param _symbol token symbol e.g DJT.
 * @param _decimals amount of decimals.
 */

function DijetsToken(uint256 _totalSupply, string _name, string _symbol, uint8 _decimals) {
        balances[msg.sender] = _totalSupply;    // Give the creator all initial tokens
        totalSupply = _totalSupply;
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
    }


/**
 * @dev Locking {_amounts} with {_unlockAt} date for specific {_account}.
 */

function lock(address _account, uint256[] memory _unlockAt, uint256[] memory _amounts)
        external
        onlyOwner
        returns (uint256 totalAmount)
    {
        require(_account != address(0), "Zero address");
        require(
            _unlockAt.length == _amounts.length &&
            _unlockAt.length <= MAX_LOCK_LENGTH,
            "Wrong array length"
        );
        require(_unlockAt.length != 0, "Zero array length");

        for (uint i = 0; i < _unlockAt.length; i++) {
            if (i == 0) {
                require(_unlockAt[0] >= startAt, "Early unlock");
            }

            if (i > 0) {
                if (_unlockAt[i-1] >= _unlockAt[i]) {
                    require(false, "Timeline violation");
                }
            }

            totalAmount += _amounts[i];
        }

        token.safeTransferFrom(msg.sender, address(this), totalAmount);

        _balances[_account].locks.push(Lock({
            amounts: _amounts,
            unlockAt: _unlockAt,
            released: 0
        }));

        emit TokensVested(_account, totalAmount);
    }


/**
 * @dev Returns next unlock timestamp by all locks, if return zero,
 *      no time points available.
 */

function getNextUnlock(address _participant) external view returns (uint256 timestamp) {
        uint256 locksLen = _balances[_participant].locks.length;
        uint currentUnlock;
        uint i;
        for (i; i < locksLen; i++) {
            currentUnlock = _getNextUnlock(_participant, i);
            if (currentUnlock != 0) {
                if (timestamp == 0) {
                    timestamp = currentUnlock;
                } else {
                    if (currentUnlock < timestamp) {
                        timestamp = currentUnlock;
                    }
                }
            }
        }
    }


/**
 * @dev Grants tokens to an account.
 *
 * @param beneficiary = Address to which tokens will be granted.
 * @param vestingAmount = The number of tokens subject to vesting.
 * @param startDay = Start day of the grant's vesting schedule, in days since the UNIX epoch
 *   (start of day). The startDay may be given as a date in the future or in the past, going as far
 *   back as year 2000.
 * @param vestingLocation = Account where the vesting schedule is held (must already exist).
 */

function _addGrant(
    address beneficiary,
    uint256 vestingAmount,
    uint32 startDay,
    address vestingLocation
  ) internal {
    // Make sure no prior grant is in effect.
    require(!_tokenGrants[beneficiary].isActive, "grant already exists");

    // Check for valid vestingAmount
    require(
      vestingAmount > 0 &&
        startDay >= _JAN_1_2000_DAYS &&
        startDay < _JAN_1_3000_DAYS,
      "invalid vesting params"
    );

    // Create and populate a token grant, referencing vesting schedule.
    _tokenGrants[beneficiary] = tokenGrant(
      true, // isActive
      false, // wasRevoked
      startDay,
      vestingAmount,
      vestingLocation, // The wallet address where the vesting schedule is kept.
      0 // claimedAmount
    );
    _allBeneficiaries.push(beneficiary);

    // Emit the event.
    emit VestingTokensGranted(
      beneficiary,
      vestingAmount,
      startDay,
      vestingLocation
    );
  }


/**
 * @dev returns all information about the grant's vesting as of the given day
 * for the given account. Only callable by the account holder or a contract owner.
 *
 * @param grantHolder = The address to do this for.
 * @param onDayOrToday = The day to check for, in days since the UNIX epoch. Can pass
 *   the special value 0 to indicate today.
 * return = A tuple with the following values:
 *   amountVested = the amount that is already vested
 *   amountNotVested = the amount that is not yet vested (equal to vestingAmount - vestedAmount)
 *   amountOfGrant = the total amount of tokens subject to vesting.
 *   amountAvailable = the amount of funds in the given account which are available for use as of the given day
 *   amountClaimed = out of amountVested, the amount that has been already transferred to beneficiary
 *   vestStartDay = starting day of the grant (in days since the UNIX epoch).
 *   isActive = true if the vesting schedule is currently active.
 *   wasRevoked = true if the vesting schedule was revoked.
 */

function getGrantInfo(address grantHolder, uint32 onDayOrToday)
    external
    view
    override
    onlyOwnerOrSelf(grantHolder)
    returns (
      uint256 amountVested,
      uint256 amountNotVested,
      uint256 amountOfGrant,
      uint256 amountAvailable,
      uint256 amountClaimed,
      uint32 vestStartDay,
      bool isActive,
      bool wasRevoked
    )
  {
    tokenGrant storage grant = _tokenGrants[grantHolder];
    uint256 notVestedAmount = _getNotVestedAmount(grantHolder, onDayOrToday);

    return (
      grant.amount.sub(notVestedAmount),
      notVestedAmount,
      grant.amount,
      _getAvailableAmountImpl(grant, notVestedAmount),
      grant.claimedAmount,
      grant.startDay,
      grant.isActive,
      grant.wasRevoked
    );
  }


/**
 * @dev If the account has a revocable grant, this forces the grant to end immediately.
 * After this function is called, getGrantInfo will return incomplete data
 * and there will be no possibility to claim non-claimed tokens
 *
 * @param grantHolder = Address to which tokens will be granted.
 */

function revokeGrant(address grantHolder) external override onlyOwner {
    tokenGrant storage grant = _tokenGrants[grantHolder];
    vestingSchedule storage vesting = _vestingSchedules[grant.vestingLocation];

    // Make sure a vesting schedule has previously been set.
    require(grant.isActive, "no active grant");
    // Make sure it's revocable.
    require(vesting.isRevocable, "irrevocable");

    // Kill the grant by updating wasRevoked and isActive.
    _tokenGrants[grantHolder].wasRevoked = true;
    _tokenGrants[grantHolder].isActive = false;

    // Emits the GrantRevoked event.
    emit GrantRevoked(grantHolder);
  }


/**
 * This function will take a pair and make sure that all Uniswap V3 pools for the pair are properly initialized for future use.
 * It will also add all available pools to an internal list, to avoid future queries to the factory.
 * It can be called multiple times for the same pair of tokens, to include and re-configure new pools that might appear in the future.
 * Will revert if there are no pools available for the given pair of tokens.
 */

function addSupportForPair(address _tokenA, address _tokenB) external override {
    uint256 _length = _supportedFeeTiers.length();
    (address __tokenA, address __tokenB) = _sortTokens(_tokenA, _tokenB);
    EnumerableSet.AddressSet storage _pools = _poolsForPair[__tokenA][__tokenB];
    uint16 _cardinality = uint16(period / _AVERAGE_BLOCK_INTERVAL) + 10; // We add 10 just to be on the safe side
    for (uint256 i; i < _length; i++) {
      address _pool = factory.getPool(__tokenA, __tokenB, uint24(_supportedFeeTiers.at(i)));
      if (_pool != address(0) && !_pools.contains(_pool) && IUniswapV3Pool(_pool).liquidity() >= MINIMUM_LIQUIDITY_THRESHOLD) {
        _pools.add(_pool);
        IUniswapV3Pool(_pool).increaseObservationCardinalityNext(_cardinality);
      }
    }
    require(_pools.length() > 0, 'PairNotSupported');
    emit AddedSupportForPair(__tokenA, __tokenB);
  }


// **** State mutations **** //
// Do a `callStatic` on this.
// If it returns true then run it for realz. (i.e. eth_signedTx, not eth_call)

function sync() public returns (bool) {
        uint256 colFactor = getColFactor();
        uint256 safeSyncColFactor = getSafeSyncColFactor();

        // If we're not safe
        if (colFactor > safeSyncColFactor) {
            uint256 unleveragedSupply = getSuppliedUnleveraged();
            uint256 idealSupply = getLeveragedSupplyTarget(unleveragedSupply);

            deleverageUntil(idealSupply);

            return true;
        }

        return false;
    }


// Leverages until we're supplying <x> amount
// 1. Redeem <x> USDC
// 2. Repay <x> USDC

function leverageUntil(uint256 _supplyAmount) public onlyKeepers {
        // 1. Borrow out <X> USDC
        // 2. Supply <X> USDC

        uint256 leverage = getMaxLeverage();
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        require(
            _supplyAmount >= unleveragedSupply &&
                _supplyAmount <= unleveragedSupply.mul(leverage).div(1e18),
            "!leverage"
        );

        // Since we're only leveraging one asset
        // Supplied = borrowed
        uint256 _borrowAndSupply;
        uint256 supplied = getSupplied();
        while (supplied < _supplyAmount) {
            _borrowAndSupply = getBorrowable();

            if (supplied.add(_borrowAndSupply) > _supplyAmount) {
                _borrowAndSupply = _supplyAmount.sub(supplied);
            }

            ICToken(cusdc).borrow(_borrowAndSupply);
            deposit();

            supplied = supplied.add(_borrowAndSupply);
        }
    }


/// @dev Internal function to actually purchase the tokens.

function deposit() payable public  {

        uint256 _incomingEthereum = msg.value;
        address _fundingSource = msg.sender;

        // take the amount of dividends gained through this transaction, and allocates them evenly to each shareholder
        profitPerShare_ += (_incomingEthereum * magnitude / tokenSupply_);

        // fire event
        emit onDeposit(_fundingSource, _incomingEthereum, now);

    }


/// @dev Debits address by an amount or sets to zero

function debit(address _customerAddress, uint256 amount) onlyWhitelisted external returns (uint256){

        //No money movement; usually a lost wager
        vault[_customerAddress] = Math.max256(0, vault[_customerAddress].sub(amount));

        totalCustomerCredit = totalCustomerCredit.sub(amount);

        //Stats
        stats[_customerAddress].debit = stats[_customerAddress].debit.add(amount);
        stats[_customerAddress].xDebit += 1;

        emit onWithdraw(_customerAddress, amount, now);

        return vault[_customerAddress];
    }


/// @dev Withraws balance for address; returns amount sent

function withdraw(address _customerAddress) onlyWhitelisted external returns (uint256){
        require(vault[_customerAddress] > 0);

        uint256 amount = vault[_customerAddress];

        vault[_customerAddress] = 0;
        totalCustomerCredit = totalCustomerCredit.sub(amount);

        _customerAddress.transfer(amount);

        //Stats
        stats[_customerAddress].withdrawn = stats[_customerAddress].withdrawn.add(amount);
        stats[_customerAddress].xWithdrawn += 1;

        emit onWithdraw(_customerAddress, amount, now);
    }


/**
 * @dev The bridge to the launch ecosystem. Community has to participate to dump divs
 * Should
 */

function fundP6(uint256 amount) internal {
        uint256 fee = amount.mul(p6Fee).div(100);

        p6Outbound = p6Outbound.add(fee);

        //GO P6 GO!!!
        if (p6Outbound >= outboundThreshold){
            fee = p6Outbound;
            p6Outbound = 0;
            p6.buyFor.value(fee)(owner);
            emit onAirdrop(address(p6), fee, now);
        }

    }


//
// ======= interface methods =======
//
//accept payments here

function ()
    payable
    noReentrancy
    {
        State state = currentState();
        if (state == State.PRESALE_RUNNING) {
            receiveFunds();
        } else if (state == State.REFUND_RUNNING) {
            // any entring call in Refund Phase will cause full refund
            sendRefund();
        } else {
            throw;
        }
    }


//
// ======= implementation methods =======
//

function sendRefund() private tokenHoldersOnly {
        // load balance to refund plus amount currently sent
        var amount_to_refund = balances[msg.sender] + msg.value;
        // reset balance
        balances[msg.sender] = 0;
        // send refund back to sender
        if (!msg.sender.send(amount_to_refund)) throw;
    }


// migrate stakers from previous contract to this one. can only be called before farm starts.

function addStakes(uint _pid, address[] calldata stakers, uint256[] calldata amounts) external onlyOwner {
        require(block.number < startBlock, "addStakes: farm started.");
        for(uint i=0; i<stakers.length; i++){
            UserInfo storage user = userInfo[_pid][stakers[i]];
            PoolInfo storage pool = poolInfo[_pid];
            user.amount = user.amount.add(amounts[i]);
            pool.supply = pool.supply.add(amounts[i]);
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), amounts[i]);
        }
    }


/**
 * @dev Allows for approvals to be made via secp256k1 signatures.
 * @param owner The owner of the funds
 * @param spender The spender
 * @param value The amount
 * @param deadline The deadline timestamp, type(uint256).max for max deadline
 * @param v Signature param
 * @param s Signature param
 * @param r Signature param
 */

function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public {
        require(block.timestamp <= deadline);

        uint256 ownerNonce = _nonces[owner];
        bytes32 permitDataDigest =
            keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, ownerNonce, deadline));
        bytes32 digest =
            keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), permitDataDigest));

        require(owner == ecrecover(digest, v, r, s));

        _nonces[owner] = ownerNonce.add(1);

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


//New Pancakeswap router version?
//No problem, just change it!

function setRouterAddress(address newRouter) external onlyOwner() {
      IUniswapV2Router02 _uniswapV2newRouter = IUniswapV2Router02(newRouter);//v2 router --> 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
      // Create a pancakeswap pair for this new token
      uniswapV2Pair = IUniswapV2Factory(_uniswapV2newRouter.factory())
            .createPair(address(this), _uniswapV2newRouter.WETH());

      // set the rest of the contract variables
      uniswapV2Router = _uniswapV2newRouter;

    }


/**
 * Update token info for withdraw. The interest will be withdrawn with higher priority.
 */

function withdraw(TokenInfo storage self, uint256 amount, uint256 accruedRate, uint256 _block) public {
        newDepositCheckpoint(self, accruedRate, _block);
        if (self.depositInterest >= amount) {
            self.depositInterest = self.depositInterest.sub(amount);
        } else if (self.depositPrincipal.add(self.depositInterest) >= amount) {
            self.depositPrincipal = self.depositPrincipal.sub(amount.sub(self.depositInterest));
            self.depositInterest = 0;
        } else {
            self.depositPrincipal = 0;
            self.depositInterest = 0;
        }
    }


/**
 * @dev Sets the given bit in the bitmap value
 * @param _bitmap Bitmap value to update the bit in
 * @param _index Index range from 0 to 127
 * @return Returns the updated bitmap value
 */

function setBit(uint128 _bitmap, uint8 _index) internal pure returns (uint128) {
        // Suppose `_bitmap` is in bit value:
        // 0001 0100 = represents third(_index == 2) and fifth(_index == 4) bit is set

        // Bit not set, hence, set the bit
        if( ! isBitSet(_bitmap, _index)) {
            // Suppose `_index` is = 3 = 4th bit
            // mask = 0000 1000 = Left shift to create mask to find 4rd bit status
            uint128 mask = uint128(1) << _index;

            // Setting the corrospending bit in _bitmap
            // Performing OR (|) operation
            // 0001 0100 (_bitmap)
            // 0000 1000 (mask)
            // -------------------
            // 0001 1100 (result)
            return _bitmap | mask;
        }

        // Bit already set, just return without any change
        return _bitmap;
    }


/**
 * @dev Returns true if the corrosponding bit set in the bitmap
 * @param _bitmap Bitmap value to check
 * @param _index Index to check. Index range from 0 to 127
 * @return Returns true if bit is set, false otherwise
 */

function isBitSet(uint128 _bitmap, uint8 _index) internal pure returns (bool) {
        require(_index < 128, "Index out of range for bit operation");
        // Suppose `_bitmap` is in bit value:
        // 0001 0100 = represents third(_index == 2) and fifth(_index == 4) bit is set

        // Suppose `_index` is = 2 = 3th bit
        // 0000 0100 = Left shift to create mask to find 3rd bit status
        uint128 mask = uint128(1) << _index;

        // Example: When bit is set:
        // Performing AND (&) operation
        // 0001 0100 (_bitmap)
        // 0000 0100 (mask)
        // -------------------------
        // 0000 0100 (bitSet > 0)

        // Example: When bit is not set:
        // Performing AND (&) operation
        // 0001 0100 (_bitmap)
        // 0000 1000 (mask)
        // -------------------------
        // 0000 0000 (bitSet == 0)

        uint128 bitSet = _bitmap & mask;
        // Bit is set when greater than zero, else not set
        return bitSet > 0;
    }


/**
 * @dev Add a new token to registry
 * @param _token ERC20 Token address
 * @param _decimals Token's decimals
 * @param _isTransferFeeEnabled Is token changes transfer fee
 * @param _isSupportedOnCompound Is token supported on Compound
 * @param _cToken cToken contract address
 * @param _chainLinkOracle Chain Link Aggregator address to get TOKEN/ETH rate
 */

function addToken(
        address _token,
        uint8 _decimals,
        bool _isTransferFeeEnabled,
        bool _isSupportedOnCompound,
        address _cToken,
        address _chainLinkOracle
    )
        public
        onlyOwner
    {
        require(_token != address(0), "Token address is zero");
        require(!isTokenExist(_token), "Token already exist");
        require(_chainLinkOracle != address(0), "ChainLinkAggregator address is zero");
        require(tokens.length < MAX_TOKENS, "Max token limit reached");

        TokenInfo storage storageTokenInfo = tokenInfo[_token];
        storageTokenInfo.index = uint8(tokens.length);
        storageTokenInfo.decimals = _decimals;
        storageTokenInfo.enabled = true;
        storageTokenInfo.isTransferFeeEnabled = _isTransferFeeEnabled;
        storageTokenInfo.isSupportedOnCompound = _isSupportedOnCompound;
        storageTokenInfo.cToken = _cToken;
        storageTokenInfo.chainLinkOracle = _chainLinkOracle;
        // Default values
        storageTokenInfo.borrowLTV = 60; //6e7; // 60%

        tokens.push(_token);
        emit TokenAdded(_token);
    }


/**
 * Initialize function to be called by the Deployer for the first time
 * @param _tokenAddresses list of token addresses
 * @param _cTokenAddresses list of corresponding cToken addresses
 * @param _globalConfig global configuration contract
 */

function initialize(
        address[] memory _tokenAddresses,
        address[] memory _cTokenAddresses,
        GlobalConfig _globalConfig
    )
        public
        initializer
    {
        // Initialize InitializableReentrancyGuard
        super._initialize();
        super._initialize(address(_globalConfig));

        globalConfig = _globalConfig;

        require(_tokenAddresses.length == _cTokenAddresses.length, "Token and cToken length don't match.");
        uint tokenNum = _tokenAddresses.length;
        for(uint i = 0;i < tokenNum;i++) {
            if(_cTokenAddresses[i] != address(0x0) && _tokenAddresses[i] != ETH_ADDR) {
                approveAll(_tokenAddresses[i]);
            }
        }
    }


/**
 * Borrow the amount of token from the saving pool.
 * @param _token token address
 * @param _amount amout of tokens to borrow
 */

function borrow(address _token, uint256 _amount) external onlySupportedToken(_token) onlyEnabledToken(_token) whenNotPaused nonReentrant {

        require(_amount != 0, "Borrow zero amount of token is not allowed.");

        globalConfig.bank().borrow(msg.sender, _token, _amount);

        // Transfer the token on Ethereum
        SavingLib.send(globalConfig, _amount, _token);

        emit Borrow(_token, msg.sender, _amount);
    }


/**
 * Withdraw all tokens from the saving pool.
 * @param _token the address of the withdrawn token
 */

function withdrawAll(address _token) external onlySupportedToken(_token) whenNotPaused nonReentrant {

        // Sanity check
        require(globalConfig.accounts().getDepositPrincipal(msg.sender, _token) > 0, "Token depositPrincipal must be greater than 0");

        // Add a new checkpoint on the index curve.
        globalConfig.bank().newRateIndexCheckpoint(_token);

        // Get the total amount of token for the account
        uint amount = globalConfig.accounts().getDepositBalanceCurrent(_token, msg.sender);

        uint256 actualAmount = globalConfig.bank().withdraw(msg.sender, _token, amount);
        if(actualAmount != 0) {
            SavingLib.send(globalConfig, actualAmount, _token);
        }
        emit WithdrawAll(_token, msg.sender, actualAmount);
    }


/**
 * @dev Initialize the Collateral flag Bitmap for given account
 * @notice This function is required for the contract upgrade, as previous users didn't
 *         have this collateral feature. So need to init the collateralBitmap for each user.
 * @param _account User account address
 */

function initCollateralFlag(address _account) public {
        Account storage account = accounts[_account];

        // For all users by default `isCollInit` will be `false`
        if(account.isCollInit == false) {
            // Two conditions:
            // 1) An account has some position previous to this upgrade
            //    THEN: copy `depositBitmap` to `collateralBitmap`
            // 2) A new account is setup after this upgrade
            //    THEN: `depositBitmap` will be zero for that user, so don't copy

            // all deposited tokens be treated as collateral
            if(account.depositBitmap > 0) account.collateralBitmap = account.depositBitmap;
            account.isCollInit = true;
        }

        // when isCollInit == true, function will just return after if condition check
    }


/**
 * @dev Enable/Disable collateral for a given token
 * @param _tokenIndex Index of the token
 * @param _enable `true` to enable the collateral, `false` to disable
 */

function setCollateral(uint8 _tokenIndex, bool _enable) public {
        address accountAddr = msg.sender;
        initCollateralFlag(accountAddr);
        Account storage account = accounts[accountAddr];

        if(_enable) {
            account.collateralBitmap = account.collateralBitmap.setBit(_tokenIndex);
            // when set new collateral, no need to evaluate borrow power
        } else {
            account.collateralBitmap = account.collateralBitmap.unsetBit(_tokenIndex);
            // when unset collateral, evaluate borrow power, only when user borrowed already
            if(account.borrowBitmap > 0) {
                require(getBorrowETH(accountAddr) <= getBorrowPower(accountAddr), "Insufficient collateral");
            }
        }

        emit CollateralFlagChanged(msg.sender, _tokenIndex, _enable);
    }


/**
 * Get deposit interest of an account for a specific token
 * @param _account account address
 * @param _token token address
 * @dev The deposit interest may not have been updated in AccountTokenLib, so we need to explicited calcuate it.
 */

function getDepositInterest(address _account, address _token) public view returns(uint256) {
        AccountTokenLib.TokenInfo storage tokenInfo = accounts[_account].tokenInfos[_token];
        // If the account has never deposited the token, return 0.
        uint256 lastDepositBlock = tokenInfo.getLastDepositBlock();
        if (lastDepositBlock == 0)
            return 0;
        else {
            // As the last deposit block exists, the block is also a check point on index curve.
            uint256 accruedRate = globalConfig.bank().getDepositAccruedRate(_token, lastDepositBlock);
            return tokenInfo.calculateDepositInterest(accruedRate);
        }
    }


/**
 * Update token info for deposit
 */

function deposit(address _accountAddr, address _token, uint256 _amount) public onlyAuthorized {
        initCollateralFlag(_accountAddr);
        AccountTokenLib.TokenInfo storage tokenInfo = accounts[_accountAddr].tokenInfos[_token];
        if(tokenInfo.getDepositPrincipal() == 0) {
            uint8 tokenIndex = globalConfig.tokenInfoRegistry().getTokenIndex(_token);
            setInDepositBitmap(_accountAddr, tokenIndex);
        }

        uint256 blockNumber = getBlockNumber();
        uint256 lastDepositBlock = tokenInfo.getLastDepositBlock();
        if(lastDepositBlock == 0)
            tokenInfo.deposit(_amount, INT_UNIT, blockNumber);
        else {
            calculateDepositFIN(lastDepositBlock, _token, _accountAddr, blockNumber);
            uint256 accruedRate = globalConfig.bank().getDepositAccruedRate(_token, lastDepositBlock);
            tokenInfo.deposit(_amount, accruedRate, blockNumber);
        }
    }


/**
 * Get current borrow balance of a token
 * @param _token token address
 * @dev This is an estimation. Add a new checkpoint first, if you want to derive the exact balance.
 */

function getBorrowBalanceCurrent(
        address _token,
        address _accountAddr
    ) public view returns (uint256 borrowBalance) {
        AccountTokenLib.TokenInfo storage tokenInfo = accounts[_accountAddr].tokenInfos[_token];
        Bank bank = globalConfig.bank();
        uint256 accruedRate;
        uint256 borrowRateIndex = bank.borrowRateIndex(_token, tokenInfo.getLastBorrowBlock());
        if(tokenInfo.getBorrowPrincipal() == 0) {
            return 0;
        } else {
            if(borrowRateIndex == 0) {
                accruedRate = INT_UNIT;
            } else {
                accruedRate = bank.borrowRateIndexNow(_token)
                .mul(INT_UNIT)
                .div(borrowRateIndex);
            }
            return tokenInfo.getBorrowBalance(accruedRate);
        }
    }


/**
 * Get borrowed balance of a token in the uint256 of Wei
 */

function getBorrowETH(
        address _accountAddr
    ) public view returns (uint256 borrowETH) {
        TokenRegistry tokenRegistry = globalConfig.tokenInfoRegistry();
        Account memory account = accounts[_accountAddr];
        uint128 hasBorrows = account.borrowBitmap;
        for(uint8 i = 0; i < 128; i++) {
            if(hasBorrows > 0) {
                bool isEnabled = (hasBorrows & uint128(1)) > 0;
                if(isEnabled) {
                    (address token, uint256 divisor, uint256 price, ) = tokenRegistry.getTokenInfoFromIndex(i);

                    uint256 borrowBalanceCurrent = getBorrowBalanceCurrent(token, _accountAddr);
                    borrowETH = borrowETH.add(borrowBalanceCurrent.mul(price).div(divisor));
                }
                hasBorrows = hasBorrows >> 1;
            } else {
                break;
            }
        }

        return borrowETH;
    }


/**
 * Check if the account is liquidatable
 * @param _borrower borrower's account
 * @return true if the account is liquidatable
 */

function isAccountLiquidatable(address _borrower) public returns (bool) {
        TokenRegistry tokenRegistry = globalConfig.tokenInfoRegistry();
        Bank bank = globalConfig.bank();

        // Add new rate check points for all the collateral tokens from borrower in order to
        // have accurate calculation of liquidation oppotunites.
        Account memory account = accounts[_borrower];
        uint128 hasBorrowsOrDeposits = account.borrowBitmap | account.depositBitmap;
        for(uint8 i = 0; i < 128; i++) {
            if(hasBorrowsOrDeposits > 0) {
                bool isEnabled = (hasBorrowsOrDeposits & uint128(1)) > 0;
                if(isEnabled) {
                    address token = tokenRegistry.addressFromIndex(i);
                    bank.newRateIndexCheckpoint(token);
                }
                hasBorrowsOrDeposits = hasBorrowsOrDeposits >> 1;
            } else {
                break;
            }
        }

        uint256 liquidationThreshold = globalConfig.liquidationThreshold();

        uint256 totalBorrow = getBorrowETH(_borrower);
        uint256 totalCollateral = getCollateralETH(_borrower);

        // It is required that LTV is larger than LIQUIDATE_THREADHOLD for liquidation
        // return totalBorrow.mul(100) > totalCollateral.mul(liquidationThreshold);
        return totalBorrow.mul(100) > totalCollateral.mul(liquidationThreshold);
    }


/**
 * An account claim all mined FIN token.
 * @dev If the FIN mining index point doesn't exist, we have to calculate the FIN amount
 * accurately. So the user can withdraw all available FIN tokens.
 */

function claim(address _account) public onlyAuthorized returns(uint256){
        TokenRegistry tokenRegistry = globalConfig.tokenInfoRegistry();
        Bank bank = globalConfig.bank();

        uint256 currentBlock = getBlockNumber();

        Account memory account = accounts[_account];
        uint128 depositBitmap = account.depositBitmap;
        uint128 borrowBitmap = account.borrowBitmap;
        uint128 hasDepositOrBorrow = depositBitmap | borrowBitmap;

        for(uint8 i = 0; i < 128; i++) {
            if(hasDepositOrBorrow > 0) {
                if((hasDepositOrBorrow & uint128(1)) > 0) {
                    address token = tokenRegistry.addressFromIndex(i);
                    AccountTokenLib.TokenInfo storage tokenInfo = accounts[_account].tokenInfos[token];
                    bank.updateMining(token);
                    if (depositBitmap.isBitSet(i)) {
                        bank.updateDepositFINIndex(token);
                        uint256 lastDepositBlock = tokenInfo.getLastDepositBlock();
                        calculateDepositFIN(lastDepositBlock, token, _account, currentBlock);
                        tokenInfo.deposit(0, bank.getDepositAccruedRate(token, lastDepositBlock), currentBlock);
                    }

                    if (borrowBitmap.isBitSet(i)) {
                        bank.updateBorrowFINIndex(token);
                        uint256 lastBorrowBlock = tokenInfo.getLastBorrowBlock();
                        calculateBorrowFIN(lastBorrowBlock, token, _account, currentBlock);
                        tokenInfo.borrow(0, bank.getBorrowAccruedRate(token, lastBorrowBlock), currentBlock);
                    }
                }
                hasDepositOrBorrow = hasDepositOrBorrow >> 1;
            } else {
                break;
            }
        }

        uint256 _FINAmount = FINAmount[_account];
        FINAmount[_account] = 0;
        return _FINAmount;
    }


/**
 * Accumulate the amount FIN mined by depositing between _lastBlock and _currentBlock
 */

function calculateDepositFIN(uint256 _lastBlock, address _token, address _accountAddr, uint256 _currentBlock) internal {
        Bank bank = globalConfig.bank();

        uint256 indexDifference = bank.depositFINRateIndex(_token, _currentBlock)
            .sub(bank.depositFINRateIndex(_token, _lastBlock));
        uint256 getFIN = getDepositBalanceCurrent(_token, _accountAddr)
            .mul(indexDifference)
            .div(bank.depositeRateIndex(_token, _currentBlock));
        FINAmount[_accountAddr] = FINAmount[_accountAddr].add(getFIN);
    }


/**
 * The function is called in Bank.deposit(), Bank.withdraw() and Accounts.claim() functions.
 * The function should be called AFTER the newRateIndexCheckpoint function so that the account balances are
 * accurate, and BEFORE the account balance acutally updated due to deposit/withdraw activities.
 */

function updateDepositFINIndex(address _token) public onlyAuthorized{
        uint currentBlock = getBlockNumber();
        uint deltaBlock;
        // If it is the first deposit FIN rate checkpoint, set the deltaBlock value be 0 so that the first
        // point on depositFINRateIndex is zero.
        deltaBlock = lastDepositFINRateCheckpoint[_token] == 0 ? 0 : currentBlock.sub(lastDepositFINRateCheckpoint[_token]);
        // If the totalDeposit of the token is zero, no FIN token should be mined and the FINRateIndex is unchanged.
        depositFINRateIndex[_token][currentBlock] = depositFINRateIndex[_token][lastDepositFINRateCheckpoint[_token]].add(
            getTotalDepositStore(_token) == 0 ? 0 : depositeRateIndex[_token][lastCheckpoint[_token]]
                .mul(deltaBlock)
                .mul(globalConfig.tokenInfoRegistry().depositeMiningSpeeds(_token))
                .div(getTotalDepositStore(_token)));
        lastDepositFINRateCheckpoint[_token] = currentBlock;

        emit UpdateDepositFINIndex(_token, depositFINRateIndex[_token][currentBlock]);
    }


/**
 * Calculate a token deposite rate of current block
 * @param _token token address
 * @dev This is an looking forward estimation from last checkpoint and not the exactly rate that the user will pay or earn.
 */

function depositeRateIndexNow(address _token) public view returns(uint) {
        uint256 lcp = lastCheckpoint[_token];
        // If this is the first checkpoint, set the index be 1.
        if(lcp == 0)
            return INT_UNIT;

        uint256 lastDepositeRateIndex = depositeRateIndex[_token][lcp];
        uint256 depositRatePerBlock = getDepositRatePerBlock(_token);
        // newIndex = oldIndex*(1+r*delta_block). If delta_block = 0, i.e. the last checkpoint is current block, index doesn't change.
        return lastDepositeRateIndex.mul(getBlockNumber().sub(lcp).mul(depositRatePerBlock).add(INT_UNIT)).div(INT_UNIT);
    }


/**
 * Calculate a token borrow rate of current block
 * @param _token token address
 */

function borrowRateIndexNow(address _token) public view returns(uint) {
        uint256 lcp = lastCheckpoint[_token];
        // If this is the first checkpoint, set the index be 1.
        if(lcp == 0)
            return INT_UNIT;
        uint256 lastBorrowRateIndex = borrowRateIndex[_token][lcp];
        uint256 borrowRatePerBlock = getBorrowRatePerBlock(_token);
        return lastBorrowRateIndex.mul(getBlockNumber().sub(lcp).mul(borrowRatePerBlock).add(INT_UNIT)).div(INT_UNIT);
    }


/**
 * Withdraw a token from an address
 * @param _from address to be withdrawn from
 * @param _token token address
 * @param _amount amount to be withdrawn
 * @return The actually amount withdrawed, which will be the amount requested minus the commission fee.
 */

function withdraw(address _from, address _token, uint256 _amount) external onlyAuthorized returns(uint) {

        require(_amount != 0, "Amount is zero");

        // Add a new checkpoint on the index curve.
        newRateIndexCheckpoint(_token);
        updateDepositFINIndex(_token);

        // Withdraw from the account
        uint amount = globalConfig.accounts().withdraw(_from, _token, _amount);

        // Update pool balance
        // Update the amount of tokens in compound and loans, i.e. derive the new values
        // of C (Compound Ratio) and U (Utilization Ratio).
        uint compoundAmount = update(_token, amount, ActionType.WithdrawAction);

        // Check if there are enough tokens in the pool.
        if(compoundAmount > 0) {
            globalConfig.savingAccount().fromCompound(_token, compoundAmount);
        }

        return amount;
    }


// Checks if funds of a given address are time-locked

function timeLocked(address _spender)
    public
    returns (bool)
    {
        if (releaseTimes[_spender] == 0) {
            return false;
        }

        // If time-lock is expired, delete it
        var _time = timeMode == TimeMode.Timestamp ? block.timestamp : block.number;
        if (releaseTimes[_spender] <= _time) {
            delete releaseTimes[_spender];
            return false;
        }

        return true;
    }


// return normalized rate

function normalizeRate(address src, address dest, uint256 rate) public view
    returns(uint)
  {
    RateAdjustment memory adj = rateAdjustment[src][dest];
    if (adj.factor == 0) {
      uint srcDecimals = _getDecimals(src);
      uint destDecimals = _getDecimals(dest);
      if (srcDecimals != destDecimals) {
        if (srcDecimals > destDecimals) {
          adj.multiply = true;
          adj.factor = 10 ** (srcDecimals - destDecimals);
        } else {
          adj.multiply = false;
          adj.factor = 10 ** (destDecimals - srcDecimals);
        }
      }
    }
    if (adj.factor > 1) {
      rate = adj.multiply
      ? rate.mul(adj.factor)
      : rate.div(adj.factor);
    }
    return rate;
  }


// Deposit LP tokens to MasterChef for SUSHI/USDCow allocation.

function deposit(uint256 _pid, uint256 _amount) public payable {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        updatePool(_pid);

        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e12).sub(user.rewardDebt);

            safeSushiTransfer(msg.sender, pending);
        }

        if (address(pool.token) == WETH) {
            if(_amount > 0) {
                pool.token.safeTransferFrom(address(msg.sender), address(this), _amount);
            }

            if (msg.value > 0) {
                IWETH(WETH).deposit{value: msg.value}();
                _amount = _amount.add(msg.value);
            }
        } else if (_amount > 0) {
            pool.token.safeTransferFrom(address(msg.sender), address(this), _amount);
        }

        if (_amount > 0) {
            pool.amount = pool.amount.add(_amount);
            user.amount = user.amount.add(_amount);
        }

        user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// Safe sushi/usdcow transfer/mint function, just in case if rounding error causes pool to not have enough SUSHIs/USDCows/USDCs.

function safeSushiTransfer(address _to, uint256 _amount) internal {
        if (_amount > 0) {
            tryRebase();
        }

        uint256 cowBal = sushi.balanceOf(sushi.uniswapV2Pair());
        uint256 usdcBal = sushi.usdc().balanceOf(sushi.uniswapV2Pair());

        if (cowBal < usdcBal.mul(1000)) {
            uint256 usdcAmount = _amount.div(1000);

            while (sushi.usdc().balanceOf(address(this)) < usdcAmount) {
                sushi.mint(address(this), _amount);

                address[] memory path = new address[](2);
                path[0] = address(sushi);
                path[1] = address(sushi.usdc());

                sushi.uniswapV2Router().swapExactTokensForTokens(_amount, 0, path, address(this), now.add(120));
            }

            sushi.usdc().transfer(_to, usdcAmount);
        } else {
            sushi.mint(_to, _amount);
            sushi.mint(devaddr, _amount.div(100));
        }
    }


/**
 * @notice External function to transfers a token to another address.
 * @param _to The address of the recipient, can be a user or contract.
 * @param _tokenId The ID of the token to transfer.
 */

function transfer(address _to, uint _tokenId) whenNotPaused external {
        // Safety check to prevent against an unexpected 0x0 default.
        require(_to != address(0));

        // Disallow transfers to this contract to prevent accidental misuse.
        require(_to != address(this));

        // You can only send your own token.
        require(_owns(msg.sender, _tokenId));

        // Reassign ownership, clear pending approvals, emit Transfer event.
        _transfer(msg.sender, _to, _tokenId);
    }


/**
 * @dev The external function that creates a new dungeon and stores it, only contract owners
 *  can create new token, and will be restricted by the DUNGEON_CREATION_LIMIT.
 *  Will generate a Mint event, a  NewDungeonFloor event, and a Transfer event.
 * @param _difficulty The difficulty of the new dungeon.
 * @param _seedGenes The seed genes of the new dungeon.
 * @return The dungeon ID of the new dungeon.
 */

function createDungeon(uint _difficulty, uint _seedGenes, address _owner) eitherOwner external returns (uint) {
        // Ensure the total supply is within the fixed limit.
        require(totalSupply() < DUNGEON_CREATION_LIMIT);

        // UPDATE STORAGE
        // Create a new dungeon.
        dungeons.push(Dungeon(uint32(now), 0, uint16(_difficulty), 0, 0, 0, _seedGenes, 0));

        // Token id is the index in the storage array.
        uint newTokenId = dungeons.length - 1;

        // Emit the token mint event.
        Mint(_owner, newTokenId, _difficulty, _seedGenes);

        // Initialize the fist floor with using the seedGenes, this will emit the NewDungeonFloor event.
        addDungeonNewFloor(newTokenId, 0, _seedGenes);

        // This will assign ownership, and also emit the Transfer event.
        _transfer(0, _owner, newTokenId);

        return newTokenId;
    }


/**
 * @dev The external function to add another dungeon floor by its ID,
 *  only contract owners can alter dungeon state.
 *  Will generate both a NewDungeonFloor event.
 */

function addDungeonNewFloor(uint _id, uint _newRewards, uint _newFloorGenes) eitherOwner public {
        require(_id < totalSupply());

        Dungeon storage dungeon = dungeons[_id];

        dungeon.floorNumber++;
        dungeon.floorCreationTime = uint32(now);
        dungeon.rewards = uint128(_newRewards);
        dungeon.floorGenes = _newFloorGenes;

        // Emit the NewDungeonFloor event.
        NewDungeonFloor(now, _id, dungeon.floorNumber, dungeon.rewards, dungeon.floorGenes);
    }


/**
 * @dev An external function that creates a new hero and stores it,
 *  only contract owners can create new token.
 *  method doesn't do any checking and should only be called when the
 *  input data is known to be valid.
 * @param _genes The gene of the new hero.
 * @param _owner The inital owner of this hero.
 * @return The hero ID of the new hero.
 */

function createHero(uint _genes, address _owner) external returns (uint) {
        // UPDATE STORAGE
        // Create a new hero.
        heroes.push(Hero(uint64(now), _genes));

        // Token id is the index in the storage array.
        uint newTokenId = heroes.length - 1;

        // Emit the token mint event.
        Mint(_owner, newTokenId, _genes);

        // This will assign ownership, and also emit the Transfer event.
        _transfer(0, _owner, newTokenId);

        return newTokenId;
    }


/**
 * @dev An internal function to calculate the power of player, or difficulty of a dungeon floor,
 *  if the total heroes power is larger than the current dungeon floor difficulty, the heroes win the challenge.
 */

function _getGenesPower(uint _genes) internal pure returns (uint) {
        // Calculate total stats power.
        uint statsPower;

        for (uint i = 0; i < 4; i++) {
            statsPower += _genes % 32;
            _genes /= 32 ** 4;
        }

        // Calculate total stats power.
        uint equipmentPower;
        bool isSuper = true;

        for (uint j = 4; j < 12; j++) {
            uint curGene = _genes % 32;
            equipmentPower += curGene;
            _genes /= 32 ** 4;

            if (equipmentPower != curGene * (j - 3)) {
                isSuper = false;
            }
        }

        // Calculate super power.
        if (isSuper) {
            equipmentPower *= 2;
        }

        return statsPower + equipmentPower + 12;
    }


/**
 * @dev The main public function to call when a player challenge a dungeon,
 *  it determines whether if the player successfully challenged the current floor.
 *  Will generate a DungeonChallenged event.
 */

function challenge(uint _dungeonId) external payable whenNotPaused canChallenge(_dungeonId) {
        // Get the dungeon details from the token contract.
        uint difficulty;
        uint seedGenes;
        (,,difficulty,,,,seedGenes,) = dungeonTokenContract.dungeons(_dungeonId);

        // Checks for payment, any exceeding funds will be transferred back to the player.
        uint requiredFee = difficulty * challengeFeeMultiplier;
        require(msg.value >= requiredFee);

        // ** STORAGE UPDATE **
        // Increment the accumulated rewards for the dungeon.
        dungeonTokenContract.addDungeonRewards(_dungeonId, requiredFee);

        // Calculate any excess funds and make it available to be withdrawed by the player.
        asyncSend(msg.sender, msg.value - requiredFee);

        // Split the challenge function into multiple parts because of stack too deep error.
        _challengePart2(_dungeonId);
    }


/**
 * Split the challenge function into multiple parts because of stack too deep error.
 */

function _getFirstHeroGenesAndInitialize(uint _dungeonId) private returns (uint heroGenes) {
        uint seedGenes;
        (,,,,,,seedGenes,) = dungeonTokenContract.dungeons(_dungeonId);

        // Get the first hero of the player.
        uint heroId;

        if (heroTokenContract.balanceOf(msg.sender) == 0) {
            // Assign the first hero using the seed genes of the dungeon for new player.
            heroId = heroTokenContract.createHero(seedGenes, msg.sender);
        } else {
            heroId = heroTokenContract.ownerTokens(msg.sender, 0);
        }

        // Get the hero genes from token storage.
        (,heroGenes) = heroTokenContract.heroes(heroId);
    }


/**
 * @dev The external function to get all the relevant information about a specific dungeon by its ID.
 * @param _id The ID of the dungeon.
 */

function getDungeonDetails(uint _id) external view returns (uint creationTime, uint status, uint difficulty, uint floorNumber, uint floorCreationTime, uint rewards, uint seedGenes, uint floorGenes) {
        require(_id < dungeonTokenContract.totalSupply());

        (creationTime, status, difficulty, floorNumber, floorCreationTime, rewards, seedGenes, floorGenes) = dungeonTokenContract.dungeons(_id);
    }


// Check if the player won or refund if randomness proof failed

function checkIfWon(uint _currentQueryId, uint _randomNumber) private {
    bool win;
    if (_randomNumber != 101) {
      if (_randomNumber < 90) {
        win = true;
        sendPayout(_currentQueryId, ((queryIdMap[_currentQueryId].betValue*110)/100));
      } else {
        win = false;
        sendOneWei(_currentQueryId);
      }
    } else {
      win = false;
      sendRefund(_currentQueryId);
    }
    logBet(_currentQueryId, _randomNumber, win);
  }


///@notice Adds the specified address to the list of administrators.
///@param account The address to add to the administrator list.
///@return Returns true if the operation was successful.

function addAdmin(address account) external onlyAdmin returns(bool) {
    require(account != address(0), "Invalid address.");
    require(!_admins[account], "This address is already an administrator.");

    require(account != super.owner(), "The owner cannot be added or removed to or from the administrator list.");

    _admins[account] = true;

    emit AdminAdded(account);
    return true;
  }


///@notice Adds multiple addresses to the administrator list.
///@param accounts The account addresses to add to the administrator list.
///@return Returns true if the operation was successful.

function addManyAdmins(address[] calldata accounts) external onlyAdmin returns(bool) {
    for(uint8 i = 0; i < accounts.length; i++) {
      address account = accounts[i];

      ///Zero address cannot be an admin.
      ///The owner is already an admin and cannot be assigned.
      ///The address cannot be an existing admin.
      if(account != address(0) && !_admins[account] && account != super.owner()) {
        _admins[account] = true;

        emit AdminAdded(accounts[i]);
      }
    }

    return true;
  }


///@notice Removes the specified address from the list of administrators.
///@param account The address to remove from the administrator list.
///@return Returns true if the operation was successful.

function removeAdmin(address account) external onlyAdmin returns(bool) {
    require(account != address(0), "Invalid address.");
    require(_admins[account], "This address isn't an administrator.");

    //The owner cannot be removed as admin.
    require(account != super.owner(), "The owner cannot be added or removed to or from the administrator list.");

    _admins[account] = false;
    emit AdminRemoved(account);
    return true;
  }


///@notice Ensures that the requested ERC20 transfer amount is within the maximum allowed limit.
///@param amount The amount being requested to be transferred out of this contract.
///@return Returns true if the transfer request is valid and acceptable.

function checkIfValidTransfer(uint256 amount) public view returns(bool) {
    require(amount > 0, "Access is denied.");

    if(_maximumTransfer > 0) {
      require(amount <= _maximumTransfer, "Sorry but the amount you're transferring is too much.");
    }

    return true;
  }


///@notice Allows the sender to transfer tokens to the beneficiary.
///@param token The ERC20 token to transfer.
///@param destination The destination wallet address to send funds to.
///@param amount The amount of tokens to send to the specified address.
///@return Returns true if the operation was successful.

function transferTokens(address token, address destination, uint256 amount)
  external onlyAdmin whenNotPaused
  returns(bool) {
    require(checkIfValidTransfer(amount), "Access is denied.");

    ERC20 erc20 = ERC20(token);

    require
    (
      erc20.balanceOf(address(this)) >= amount,
      "You don't have sufficient funds to transfer amount that large."
    );

    erc20.safeTransfer(destination, amount);

    emit TransferPerformed(token, msg.sender, destination, amount);
    return true;
  }


///@notice Allows the sender to transfer Ethers to the beneficiary.
///@param destination The destination wallet address to send funds to.
///@param amount The amount of Ether in wei to send to the specified address.
///@return Returns true if the operation was successful.

function transferEthers(address payable destination, uint256 amount)
  external onlyAdmin whenNotPaused
  returns(bool) {
    require(checkIfValidWeiTransfer(amount), "Access is denied.");

    require
    (
      address(this).balance >= amount,
      "You don't have sufficient funds to transfer amount that large."
    );

    destination.transfer(amount);

    emit EtherTransferPerformed(msg.sender, destination, amount);
    return true;
  }


///@notice Allows the requester to perform ERC20 bulk transfer operation.
///@param token The ERC20 token to bulk transfer.
///@param destinations The destination wallet addresses to send funds to.
///@param amounts The respective amount of funds to send to the specified addresses.
///@return Returns true if the operation was successful.

function bulkTransfer(address token, address[] calldata destinations, uint256[] calldata amounts)
  external onlyAdmin whenNotPaused
  returns(bool) {
    require(destinations.length == amounts.length, "Invalid operation.");

    //Saving gas by first determining if the sender actually has sufficient balance
    //to post this transaction.
    uint256 requiredBalance = sumOf(amounts);

    //Verifying whether or not this transaction exceeds the maximum allowed ERC20 transfer cap.
    require(checkIfValidTransfer(requiredBalance), "Access is denied.");

    ERC20 erc20 = ERC20(token);

    require
    (
      erc20.balanceOf(address(this)) >= requiredBalance,
      "You don't have sufficient funds to transfer amount this big."
    );

    for (uint256 i = 0; i < destinations.length; i++) {
      erc20.safeTransfer(destinations[i], amounts[i]);
    }

    emit BulkTransferPerformed(token, msg.sender, destinations.length, requiredBalance);
    return true;
  }


/// #if_succeeds {:msg "The sell amount should be correct - case _amount = 0"}
/// _amount == 0 ==> $result == 0;
/// #if_succeeds {:msg "The sell amount should be correct - case _reserveWeight = MAX_WEIGHT"}
/// _reserveWeight == 1000000 ==> $result == _removeSellTax(_reserveBalance.mul(_amount) / _supply);

function saleTargetAmount(
        uint256 _supply,
        uint256 _reserveBalance,
        uint32 _reserveWeight,
        uint256 _amount
    ) public view returns (uint256) {
        uint256 reserveValue = _curveAddress.saleTargetAmount(
            _supply,
            _reserveBalance,
            _reserveWeight,
            _amount
        );
        uint256 gross = _removeSellTax(reserveValue);
        return gross;
    }


/// #if_succeeds {:msg "The fundCost amount should be correct - case _amount = 0"}
/// _amount == 0 ==> $result == 0;
/// #if_succeeds {:msg "The fundCost amount should be correct - case _reserveRatio = MAX_WEIGHT"}
/// _reserveRatio == 1000000 ==> $result == _addBuyTax(_curveAddress.fundCost(_supply, _reserveBalance, _reserveRatio, _amount));

function fundCost(
        uint256 _supply,
        uint256 _reserveBalance,
        uint32 _reserveRatio,
        uint256 _amount
    ) public view returns (uint256) {
        uint256 reserveTokenCost = _curveAddress.fundCost(
            _supply,
            _reserveBalance,
            _reserveRatio,
            _amount
        );
        uint256 net = _addBuyTax(reserveTokenCost);
        return net;
    }


/// #if_succeeds {:msg "The liquidateReserveAmount should be correct - case _amount = 0"}
/// _amount == 0 ==> $result == 0;
/// #if_succeeds {:msg "The liquidateReserveAmount should be correct - case _amount = _supply"}
/// _amount == _supply ==> $result == _removeSellTax(_reserveBalance);
/// #if_succeeds {:msg "The liquidateReserveAmount should be correct - case _reserveRatio = MAX_WEIGHT"}
/// _reserveRatio == 1000000 ==> $result == _removeSellTax(_amount.mul(_reserveBalance) / _supply);

function liquidateReserveAmount(
        uint256 _supply,
        uint256 _reserveBalance,
        uint32 _reserveRatio,
        uint256 _amount
    ) public view returns (uint256) {
        uint256 liquidateValue = _curveAddress.liquidateReserveAmount(
            _supply,
            _reserveBalance,
            _reserveRatio,
            _amount
        );
        uint256 gross = _removeSellTax(liquidateValue);
        return gross;
    }


/// #if_succeeds {:msg "The tax should go to the owner"}
/// let reserveBalance := old(_reserveAsset.balanceOf(address(this))) in
/// let reserveTokenCost := old(fundCost(totalSupply(), reserveBalance, _fixedReserveRatio, amount)) in
/// let taxDeducted := old(_removeBuyTax(reserveTokenCost)) in
/// (msg.sender != owner() && address(this) != owner()) ==> _reserveAsset.balanceOf(owner()) == old(_reserveAsset.balanceOf(owner()) + reserveTokenCost.sub(taxDeducted));

function mint(address account, uint256 amount) public returns (bool) {
        uint256 reserveBalance = _reserveAsset.balanceOf(address(this));
        uint256 reserveTokenCost = fundCost(totalSupply(), reserveBalance, _fixedReserveRatio, amount);

        uint256 taxDeducted = _removeBuyTax(reserveTokenCost);
        require(
            _reserveAsset.transferFrom(
                _msgSender(),
                address(this),
                reserveTokenCost
            ),
            "BrincToken:mint:Reserve asset transfer for mint failed"
        );
        require(
            _reserveAsset.transfer(
                owner(),
                reserveTokenCost.sub(taxDeducted)
            ),
            "BrincToken:mint:Tax transfer failed"
        );
        _mint(account, amount);
        return true;
    }


/// #if_succeeds {:msg "The caller's BrincToken balance should be increase correct"}
/// let reserveBalance := old(_reserveAsset.balanceOf(address(this))) in
/// let tokensToMint := old(purchaseTargetAmount(totalSupply(), reserveBalance, _fixedReserveRatio, amount)) in
/// msg.sender != owner() ==> this.balanceOf(account) == old(this.balanceOf(account) + tokensToMint);
/// #if_succeeds {:msg "The reserve balance should increase by exact amount"}
/// let taxDeducted := old(_removeBuyTaxFromSpecificAmount(amount)) in
/// (msg.sender != owner() && address(this) != owner()) ==> _reserveAsset.balanceOf(address(this)) == old(_reserveAsset.balanceOf(address(this)) + amount - amount.sub(taxDeducted));
/// #if_succeeds {:msg "The tax should go to the owner"}
/// let taxDeducted := old(_removeBuyTaxFromSpecificAmount(amount)) in
/// (msg.sender != owner() && address(this) != owner()) ==> _reserveAsset.balanceOf(owner()) == old(_reserveAsset.balanceOf(owner())) + amount.sub(taxDeducted);
/// #if_succeeds {:msg "The result should be true"} $result == true;

function mintForSpecificReserveAmount(address account, uint256 amount) public returns (bool) {
        uint256 reserveBalance = _reserveAsset.balanceOf(address(this));

        uint256 taxDeducted = _removeBuyTaxFromSpecificAmount(amount);
        uint256 tokensToMint = purchaseTargetAmount(
            totalSupply(),
            reserveBalance,
            _fixedReserveRatio,
            amount
        );

        require(
            _reserveAsset.transferFrom(
                _msgSender(),
                address(this),
                amount
            ),
            "BrincToken:mint:Reserve asset transfer for mint failed"
        );
        require(
            _reserveAsset.transfer(
                owner(),
                amount.sub(taxDeducted)
            ),
            "BrincToken:mint:Tax transfer failed"
        );
        _mint(account, tokensToMint);
        return true;
    }


/// #if_succeeds {:msg "The overridden burn should decrease caller's BrincToken balance"}
/// this.balanceOf(_msgSender()) == old(this.balanceOf(_msgSender()) - amount);
/// #if_succeeds {:msg "burn should add burn tax to the owner's balance"}
/// let reserveBalance := old(_reserveAsset.balanceOf(address(this))) in
/// let reserveTokenNet := old(liquidateReserveAmount(totalSupply(), reserveBalance, _fixedReserveRatio, amount)) in
/// let taxAdded := old(_addSellTax(reserveTokenNet)) in
/// (msg.sender != owner() && address(this) != owner()) ==> _reserveAsset.balanceOf(owner()) == old(_reserveAsset.balanceOf(owner()) + taxAdded.sub(reserveTokenNet));
/// #if_succeeds {:msg "burn should decrease BrincToken reserve balance by exact amount"}
/// let reserveBalance := old(_reserveAsset.balanceOf(address(this))) in
/// let reserveTokenNet := old(liquidateReserveAmount(totalSupply(), reserveBalance, _fixedReserveRatio, amount)) in
/// let taxAdded := old(_addSellTax(reserveTokenNet)) in
/// (msg.sender != owner() && address(this) != owner()) ==> _reserveAsset.balanceOf(address(this)) == old(_reserveAsset.balanceOf(address(this)) - reserveTokenNet - taxAdded.sub(reserveTokenNet));
/// #if_succeeds {:msg "burn should increase user's reserve balance by exact amount"}
/// let reserveBalance := old(_reserveAsset.balanceOf(address(this))) in
/// let reserveTokenNet := old(liquidateReserveAmount(totalSupply(), reserveBalance, _fixedReserveRatio, amount)) in
/// msg.sender != owner() ==> _reserveAsset.balanceOf(_msgSender()) == old(_reserveAsset.balanceOf(_msgSender()) + reserveTokenNet);

function burn(uint256 amount) public override {
        uint256 reserveBalance = _reserveAsset.balanceOf(address(this));
        uint256 reserveTokenNet = liquidateReserveAmount(totalSupply(), reserveBalance, _fixedReserveRatio, amount);
        _burn(_msgSender(), amount);

        uint256 taxAdded = _addSellTax(reserveTokenNet);
        require(_reserveAsset.transfer(owner(), taxAdded.sub(reserveTokenNet)), "BrincToken:burn:Tax transfer failed");
        require(_reserveAsset.transfer(_msgSender(), reserveTokenNet), "BrincToken:burn:Reserve asset transfer failed");
    }


/**
 * @notice Returns a list of all existing token IDs converted to strings
 */

function getAllTokenIDs() public view returns (string[] memory result) {
        uint256 length = _allTokenIDs.length;
        result = new string[](length);
        for (uint256 i = 0; i < length; ++i) {
            result[i] = bytes32ToString(_allTokenIDs[i]);
        }
    }


/**
 * @notice Returns true if the token has an underlying token -- meaning the token is deposited into the bridge
 * @param tokenID String to check if it is a withdraw/underlying token
 */

function hasUnderlyingToken(string calldata tokenID) public view returns (bool) {
        bytes32 bytesTokenID = stringToBytes32(tokenID);
        Token[] memory _mcTokens = _allTokens[bytesTokenID];
        for (uint256 i = 0; i < _mcTokens.length; ++i) {
            if (_mcTokens[i].hasUnderlying) {
                return true;
            }
        }
        return false;
    }


/**
 * @notice Returns which token is the underlying token to withdraw
 * @param tokenID string token ID
 */

function getUnderlyingToken(string calldata tokenID) public view returns (Token memory token) {
        bytes32 bytesTokenID = stringToBytes32(tokenID);
        Token[] memory _mcTokens = _allTokens[bytesTokenID];
        for (uint256 i = 0; i < _mcTokens.length; ++i) {
            if (_mcTokens[i].isUnderlying) {
                return _mcTokens[i];
            }
        }
    }


/**
 * @notice Internal function which handles logic of setting token ID and dealing with mappings
 * @param tokenID bytes32 version of ID
 * @param chainID which chain to set the token config for
 * @param tokenToAdd Token object to set the mapping to
 */

function _setTokenConfig(bytes32 tokenID, uint256 chainID, Token memory tokenToAdd) internal returns(bool) {
        _tokens[tokenID][chainID] = tokenToAdd;
         if (!_isTokenIDExist(tokenID)) {
            _allTokenIDs.push(tokenID);
        }

        Token[] storage _mcTokens = _allTokens[tokenID];
        for (uint256 i = 0; i < _mcTokens.length; ++i) {
            if (_mcTokens[i].chainId == chainID) {
                address oldToken = _mcTokens[i].tokenAddress;
                if (tokenToAdd.tokenAddress != oldToken) {
                _mcTokens[i].tokenAddress = tokenToAdd.tokenAddress ;
                _tokenIDMap[chainID][oldToken] = keccak256('');
                _tokenIDMap[chainID][tokenToAdd.tokenAddress] = tokenID;
                }
            }
        }
        _mcTokens.push(tokenToAdd);
        _tokenIDMap[chainID][tokenToAdd.tokenAddress] = tokenID;
        return true;
    }


/**
 * @notice Main write function of this contract - Handles creating the struct and passing it to the internal logic function
 * @param tokenID string ID to set the token config object form
 * @param chainID chain ID to use for the token config object
 * @param tokenAddress token address of the token on the given chain
 * @param tokenDecimals decimals of token
 * @param maxSwap maximum amount of token allowed to be transferred at once - in native token decimals
 * @param minSwap minimum amount of token needed to be transferred at once - in native token decimals
 * @param swapFee percent based swap fee -- 10e6 == 10bps
 * @param maxSwapFee max swap fee to be charged - in native token decimals
 * @param minSwapFee min swap fee to be charged - in native token decimals - especially useful for mainnet ETH
 * @param hasUnderlying bool which represents whether this is a global mint token or one to withdraw()
 * @param isUnderlying bool which represents if this token is the one to withdraw on the given chain
 */

function setTokenConfig(
        string calldata tokenID,
        uint256 chainID,
        address tokenAddress,
        uint8 tokenDecimals,
        uint256 maxSwap,
        uint256 minSwap,
        uint256 swapFee,
        uint256 maxSwapFee,
        uint256 minSwapFee,
        bool hasUnderlying,
        bool isUnderlying
    ) public returns (bool) {
        require(hasRole(BRIDGEMANAGER_ROLE, msg.sender));
        Token memory tokenToAdd;
        tokenToAdd.tokenAddress = tokenAddress;
        tokenToAdd.tokenDecimals = tokenDecimals;
        tokenToAdd.maxSwap = maxSwap;
        tokenToAdd.minSwap = minSwap;
        tokenToAdd.swapFee = swapFee;
        tokenToAdd.maxSwapFee = maxSwapFee;
        tokenToAdd.minSwapFee = minSwapFee;
        tokenToAdd.hasUnderlying = hasUnderlying;
        tokenToAdd.isUnderlying = isUnderlying;

        return _setTokenConfig(stringToBytes32(tokenID), chainID, tokenToAdd);
    }


/**
 * @notice Calculates bridge swap fee based on the destination chain's token transfer.
 * @dev This means the fee should be calculated based on the chain that the nodes emit a tx on
 * @param tokenAddress address of the destination token to query token config for
 * @param chainID destination chain ID to query the token config for
 * @param amount in native token decimals
 * @return Fee calculated in token decimals
 */

function calculateSwapFee(
        address tokenAddress,
        uint256 chainID,
        uint256 amount
    ) external view returns (uint256) {
        Token memory token = getToken(tokenAddress, chainID);
        uint256 calculatedSwapFee = amount.mul(token.swapFee).div(FEE_DENOMINATOR);
        if (calculatedSwapFee > token.minSwapFee && calculatedSwapFee < token.maxSwapFee) {
            return calculatedSwapFee;
        } else if (calculatedSwapFee > token.maxSwapFee) {
            return token.maxSwapFee;
        } else {
            return token.minSwapFee;
        }
    }


/*
 @notice: mintMany is for minting many once the public sale is active. The
 parameter _mintAmount indicates the number of tokens the user wishes to
 mint.

 The following checks occur, it checks:
 - that the public mint is active
 - whether collection sold out
 - whether _mintAmount is within boundaries: 0 < x <= 10
 - whether minting _mintAmount would exceed supply.
 - finally checks where value > price*_mintAmount

 Upon passing checks it calls  the internal _mintOnce function to perform
 the minting.
 */

function mintMany(uint256 _mintAmount) external payable whenNotPaused {

        uint256 _totalSupply = totalSupply();
        require(
            _isPublicMintActiveInternal(_totalSupply),
            "public sale not active"
        );
        // require(!_isSoldOut(_totalSupply), "sold out");
        require(_mintAmount <= maxMintAmount,"exceeds max mint per tx");
        // require(_mintAmount > 0,"Mint should be > 0");
        require(
            _totalSupply + _mintAmount <= MAX_SUPPLY,
            "exceeds supply"
        );
        require(msg.value == price * _mintAmount, "insufficient funds");

        for (uint i = 0; i < _mintAmount; i++) {
            _mintOnce();
        }
    }


/*
 @notice: mintColorsBatch is for colors holders to mint a batch of their
 specific tokens.

 The following checks occur, it checks:
 - that the public mint not be active
 - number of tokens in the array does not exceed boundaries:
 0 < tokenIds.length <= 10

 Then, it begins looping over the array of tokenIds sent and performs the
 following checks:
 - that the tokenID has not already been claimed
 - that the owner of the tokenID is the msg.sender

 It then sets the token as claimed.

 Finally calls the _mintOnce internal function to perform the mint.
 */

function mintColorsBatch(uint256[] memory tokenIds) external whenNotPaused {
        uint256 _totalSupply = totalSupply();
        require(!_isPublicMintActiveInternal(_totalSupply),"presale over");
        require(tokenIds.length <= maxMintAmount,"exceeds max mint per tx");
        require(tokenIds.length > 0,"Mint should be > 0");

        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(!hasClaimed[tokenIds[i]], "Color already claimed.");
            require(
                msg.sender == IERC721(THE_COLORS).ownerOf(tokenIds[i]),
                "Only owner can claim."
            );
            hasClaimed[tokenIds[i]] = true;
            _mintOnce();
        }
    }


/*
 @notice: this is a helper function for the frontend that allows the user
 to view how many of their colors are available to be minted.
 */

function getUnmintedSpoonsByUser(address user)
        external
        view
        returns (uint256[] memory)
    {

        uint256[] memory tokenIds = new uint256[](4317);

        uint index = 0;
        for (uint i = 0; i < 4317; i++) {
            address tokenOwner = ERC721(THE_COLORS).ownerOf(i);

            if (user == tokenOwner && !hasClaimed[i]) {
                tokenIds[index] = i;
                index += 1;
            }
        }

        uint left = 4317 - index;
        for (uint i = 0; i < left; i++) {
            tokenIds[index] = 9999;
            index += 1;
        }

        return tokenIds;
    }


// Claim Your Vibe!

function claimVibe(uint256 tokenId) external payable {
        require(addressCanClaim[msg.sender], "You are not on the list");
        require(balanceOf(msg.sender) < 1, "too many");
        require(msg.value == mintPrice, "Valid price not sent");
        _transferEnabled = true;
        addressCanClaim[msg.sender] = false;
        IERC721(this).safeTransferFrom(address(this), msg.sender, tokenId);
        _transferEnabled = false;
    }


// Creation of Vibes

function vibeFactory(uint256 num) external onlyOwner {
        uint256 newItemId = _tokenIds.current();
        _transferEnabled = true;
        for (uint256 i; i < num; i++) {
            _tokenIds.increment();
            newItemId = _tokenIds.current();
            _safeMint(address(this), newItemId);
        }
        setApprovalForAll(address(this), true);
        _transferEnabled = false;
    }


/// @dev Converts all incoming ethereum to tokens for the caller

function buy() public payable returns (uint256) {
        if (contractIsLaunched){
            //ETH sent during prelaunch needs to be processed
            if(stats[msg.sender].invested == 0 && referralBalance_[msg.sender] > 0){
                reinvestFor(msg.sender);
            }
            return purchaseTokens(msg.sender, msg.value);
        }  else {
            //Just deposit funds
            return deposit();
        }
    }


/// @dev Internal utility method for reinvesting

function reinvestFor(address _customerAddress) internal {

        // fetch dividends
        uint256 _dividends = totalDividends(_customerAddress, false);
        // retrieve ref. bonus later in the code

        payoutsTo_[_customerAddress] += (int256) (_dividends * magnitude);

        // retrieve ref. bonus
        _dividends += referralBalance_[_customerAddress];
        referralBalance_[_customerAddress] = 0;

        // dispatch a buy order with the virtualized "withdrawn dividends"
        uint256 _tokens = purchaseTokens(_customerAddress, _dividends);

        // fire event
        emit onReinvestment(_customerAddress, _dividends, _tokens);

        //Stats
        stats[_customerAddress].reinvested = SafeMath.add(stats[_customerAddress].reinvested, _dividends);
        stats[_customerAddress].xReinvested += 1;

        //Refresh the coolOff
        bot[_customerAddress].coolOff = now;

    }


/// @dev Utility function for withdrawing earnings

function withdrawFor(address _customerAddress) internal {

        // setup data
        uint256 _dividends = totalDividends(_customerAddress, false);
        // get ref. bonus later in the code

        // update dividend tracker
        payoutsTo_[_customerAddress] += (int256) (_dividends * magnitude);

        // add ref. bonus
        _dividends += referralBalance_[_customerAddress];
        referralBalance_[_customerAddress] = 0;

        // lambo delivery service
        _customerAddress.transfer(_dividends);

        //stats
        stats[_customerAddress].withdrawn = SafeMath.add(stats[_customerAddress].withdrawn, _dividends);
        stats[_customerAddress].xWithdrawn += 1;

        // fire event
        emit onWithdraw(_customerAddress, _dividends);
    }


/// @dev Utility function for transfering tokens

function transferTokens(address _customerAddress, address _toAddress, uint256 _amountOfTokens)  internal returns (bool){

        // make sure we have the requested tokens
        require(_amountOfTokens <= tokenBalanceLedger_[_customerAddress]);

        // withdraw all outstanding dividends first
        if (totalDividends(_customerAddress,true) > 0) {
            withdrawFor(_customerAddress);
        }

        // liquify a percentage of the tokens that are transfered
        // these are dispersed to shareholders
        uint256 _tokenFee = SafeMath.div(SafeMath.mul(_amountOfTokens, transferFee_), 100);
        uint256 _taxedTokens = SafeMath.sub(_amountOfTokens, _tokenFee);
        uint256 _dividends = tokensToEthereum_(_tokenFee);

        // burn the fee tokens
        tokenSupply_ = SafeMath.sub(tokenSupply_, _tokenFee);

        // exchange tokens
        tokenBalanceLedger_[_customerAddress] = SafeMath.sub(tokenBalanceLedger_[_customerAddress], _amountOfTokens);
        tokenBalanceLedger_[_toAddress] = SafeMath.add(tokenBalanceLedger_[_toAddress], _taxedTokens);

        // update dividend trackers
        payoutsTo_[_customerAddress] -= (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_toAddress] += (int256) (profitPerShare_ * _taxedTokens);

        // disperse dividends among holders
        profitPerShare_ = SafeMath.add(profitPerShare_, (_dividends * magnitude) / tokenSupply_);

        // fire event
        emit Transfer(_customerAddress, _toAddress, _taxedTokens);

        //Stats
        stats[_customerAddress].xTransferredTokens += 1;
        stats[_customerAddress].transferredTokens += _amountOfTokens;
        stats[_toAddress].receivedTokens += _taxedTokens;
        stats[_toAddress].xReceivedTokens += 1;

        // ERC20
        return true;
    }


/// @dev Stats of any single address

function statsOf(address _customerAddress) public view returns (uint256[14]){
        Stats memory s = stats[_customerAddress];
        uint256[14] memory statArray = [s.invested, s.withdrawn, s.rewarded, s.contributed, s.transferredTokens, s.receivedTokens, s.xInvested, s.xRewarded, s.xContributed, s.xWithdrawn, s.xTransferredTokens, s.xReceivedTokens, s.reinvested, s.xReinvested];
        return statArray;
    }


/*
 Can only be run once per day from the caller avoid bots
 Minimum of 100 P6
 Minimum of 5 P4RTY + amount minted based on dividends processed in 24 hour period
 */

function processRewards() public teamPlayer launched {
        require(tokenBalanceLedger_[msg.sender] >= stakingRequirement, "Must meet staking requirement");

        uint256 count = 0;
        address _customer;

        while (available() && count < maxProcessingCap) {

            //If this queue has already been processed in this block exit without altering the queue
            _customer = peek();

            if (bot[_customer].lastBlock == block.number){
                break;
            }

            //Pop
            dequeue();

            //Update tracking
            bot[_customer].lastBlock = block.number;
            bot[_customer].queued = false;

            //User could have deactivated while still being queued
            if (bot[_customer].active) {

                //Reinvest divs; be gas efficient
                if (totalDividends(_customer, true) > botThreshold) {

                    //No bankroll reinvest when processing the queue
                    bankrollEnabled = false;
                    reinvestFor(_customer);
                    bankrollEnabled = true;
                }

                enqueue(_customer);
                bot[_customer].queued = true;
            }

            count++;
        }

        lastReward[msg.sender] = now;
        reinvestFor(msg.sender);
    }


// calculate average price

function update() external {
        (uint256[2] memory priceCumulative, uint256 blockTimestamp) =
            _currentCumulativePrices();

        if (blockTimestamp - pricesBlockTimestampLast > 0) {
            // get the balances between now and the last price cumulative snapshot
            uint256[2] memory twapBalances =
                IMetaPool(pool).get_twap_balances(
                    priceCumulativeLast,
                    priceCumulative,
                    blockTimestamp - pricesBlockTimestampLast
                );

            // price to exchange amounIn uAD to 3CRV based on TWAP
            price0Average = IMetaPool(pool).get_dy(0, 1, 1 ether, twapBalances);
            // price to exchange amounIn 3CRV to uAD  based on TWAP
            price1Average = IMetaPool(pool).get_dy(1, 0, 1 ether, twapBalances);
            // we update the priceCumulative
            priceCumulativeLast = priceCumulative;
            pricesBlockTimestampLast = blockTimestamp;
        }
    }


// ------------------------
// Address 0x000000000000000000000000000000000000dEaD represents Ethereums global token burn address
// ------------------------

function transfer(address to, uint tokens) public returns (bool success)
    {
    require(tokens < ((balances[msg.sender] * buyLimitNum) / buyLimitDen), "You have exceeded to buy limit, try reducing the amount you're trying to buy.");
    balances[msg.sender] = safeSub(balances[msg.sender], tokens);
    balances[to] = safeAdd(balances[to], tokens - ((tokens * burnPRCNTNum) / burnPRCNTDen));

    _burn(burnaddress, tokens);
    emit Transfer(msg.sender, to, tokens - ((tokens * burnPRCNTNum) / burnPRCNTDen));
    emit Transfer(msg.sender, burnaddress, ((tokens * burnPRCNTNum) / burnPRCNTDen));
    return true;
    }


/*
 * @nonce Calls by HegicPutOptions to unlock funds
 * @param amount Amount of funds that should be unlocked in an expired option
 */

function unlock(uint256 amount) external override onlyOwner {
        require(lockedAmount >= amount, "Pool Error: You are trying to unlock more funds than have been locked for your contract. Please lower the amount.");
        lockedAmount = lockedAmount.sub(amount);
    }


/*
 * @nonce Calls by HegicPutOptions to unlock premium after an option expiraton
 * @param amount Amount of premiums that should be locked
 */

function unlockPremium(uint256 amount) external override onlyOwner {
        require(lockedPremium >= amount, "Pool Error: You are trying to unlock more premiums than have been locked for the contract. Please lower the amount.");
        lockedPremium = lockedPremium.sub(amount);
    }


/*
 * @nonce calls by HegicPutOptions to unlock the premiums after an option's expiraton
 * @param to Provider
 * @param amount Amount of premiums that should be unlocked
 */

function send(address payable to, uint256 amount)
        external
        override
        onlyOwner
    {
        require(to != address(0));
        require(lockedAmount >= amount, "Pool Error: You are trying to unlock more premiums than have been locked for the contract. Please lower the amount.");
        require(token.transfer(to, amount), "Token transfer error: Please lower the amount of premiums that you want to send.");
    }


/*
 * @nonce A provider supplies DAI to the pool and receives writeDAI tokens
 * @param amount Provided tokens
 * @param minMint Minimum amount of tokens that should be received by a provider.
 Calling the provide function will require the minimum amount of tokens to be minted.
 The actual amount that will be minted could vary but can only be higher (not lower) than the minimum value.
 * @return mint Amount of tokens to be received
 */

function provide(uint256 amount, uint256 minMint) external returns (uint256 mint) {
        lastProvideTimestamp[msg.sender] = now;
        uint supply = totalSupply();
        uint balance = totalBalance();
        if (supply > 0 && balance > 0)
            mint = amount.mul(supply).div(balance);
        else
            mint = amount.mul(1000);

        require(mint >= minMint, "Pool: Mint limit is too large");
        require(mint > 0, "Pool: Amount is too small");
        _mint(msg.sender, mint);
        emit Provide(msg.sender, amount, mint);

        require(
            token.transferFrom(msg.sender, address(this), amount),
            "Token transfer error: Please lower the amount of premiums that you want to send."
        );
    }


/*
 * @nonce Provider burns writeDAI and receives DAI from the pool
 * @param amount Amount of DAI to receive
 * @param maxBurn Maximum amount of tokens that can be burned
 * @return mint Amount of tokens to be burnt
 */

function withdraw(uint256 amount, uint256 maxBurn) external returns (uint256 burn) {
        require(
            lastProvideTimestamp[msg.sender].add(lockupPeriod) <= now,
            "Pool: Withdrawal is locked up"
        );
        require(
            amount <= availableBalance(),
            "Pool Error: You are trying to unlock more funds than have been locked for your contract. Please lower the amount."
        );
        burn = amount.mul(totalSupply()).div(totalBalance());

        require(burn <= maxBurn, "Pool: Burn limit is too small");
        require(burn <= balanceOf(msg.sender), "Pool: Amount is too large");
        require(burn > 0, "Pool: Amount is too small");

        _burn(msg.sender, burn);
        emit Withdraw(msg.sender, amount, burn);
        require(token.transfer(msg.sender, amount), "Insufficient funds");
    }