// Emergency function call

function execute(address _target, bytes memory _data)
        public
        payable
        returns (bytes memory response)
    {
        require(msg.sender == timelock, "!timelock");

        require(_target != address(0), "!target");

        // call contract in current context
        assembly {
            let succeeded := delegatecall(
                sub(gas(), 5000),
                _target,
                add(_data, 0x20),
                mload(_data),
                0,
                0
            )
            let size := returndatasize()

            response := mload(0x40)
            mstore(
                0x40,
                add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))
            )
            mstore(response, size)
            returndatacopy(add(response, 0x20), 0, size)

            switch iszero(succeeded)
                case 1 {
                    // throw if delegatecall failed
                    revert(add(response, 0x20), size)
                }
        }
    }


/**
 * @notice This function is only callable after the curve contract has been
 *         initialized.
 * @param  _amount The amount of tokens a user wants to buy
 * @return uint256 The cost to buy the _amount of tokens in ETH
 */

function buyPrice(uint256 _amount)
        public
        view
        isCurveActive()
        returns (uint256)
    {
        // Getting the current DAI cost for token amount
        uint256 daiCost = curve_.buyPrice(_amount);
        // Returning the required ETH to buy DAI amount
        return router_.getAmountsIn(
            daiCost,
            getPath(true)
        )[0];
    }


/**
 * @notice This function is only callable after the curve contract has been
 *         initialized.
 * @param  _amount The amount of tokens a user wants to sell
 * @return uint256 The reward for selling the _amount of tokens in ETH
 */

function sellReward(uint256 _amount)
        public
        view
        isCurveActive()
        returns (uint256)
    {
        // Getting the current DAI reward for token amount
        uint256 daiReward = curve_.sellReward(_amount);
        // Returning the ETH reward for token sale
        return router_.getAmountsIn(
            daiReward,
            getPath(true)
        )[0];
    }


/**
 * @param  _buy If the path is for a buy or sell transaction
 * @return address[] The path for the transaction
 */

function getPath(bool _buy) public view returns(address[] memory) {
        address[] memory buyPath = new address[](2);
        if(_buy) {
            buyPath[0] = router_.WETH();
            buyPath[1] = address(dai_);
        } else {
            buyPath[0] = address(dai_);
            buyPath[1] = router_.WETH();
        }

        return buyPath;
    }


/**
 * @param  _tokenAmount The amount of BZZ tokens the user would like to
 *         buy from the curve.
 * @param  _maxDaiSpendAmount The max amount of collateral (DAI) the user
 *         is willing to spend to buy the amount of tokens.
 * @param  _deadline Unix timestamp after which the transaction will
 *         revert. - Taken from Uniswap documentation:
 *         https://uniswap.org/docs/v2/smart-contracts/router02/#swapethforexacttokens
 * @return bool If the transaction completed successfully.
 * @notice Before this function is called the caller does not need to
 *         approve the spending of anything. Please assure that the amount
 *         of ETH sent with this transaction is sufficient by first calling
 *         `buyPrice` with the same token amount. Add your % slippage to
 *         the max dai spend amount (you can get the expected amount by
 *         calling `buyPrice` on the curve contract).
 */

function mint(
        uint256 _tokenAmount,
        uint256 _maxDaiSpendAmount,
        uint _deadline
    )
        external
        payable
        isCurveActive()
        mutex()
        returns (bool)
    {
        (uint256 daiNeeded, uint256 ethReceived) = _commonMint(
            _tokenAmount,
            _maxDaiSpendAmount,
            _deadline,
            msg.sender
        );
        // Emitting event with all important info
        emit mintTokensWithEth(
            msg.sender,
            _tokenAmount,
            daiNeeded,
            ethReceived,
            _maxDaiSpendAmount
        );
        // Returning that the mint executed successfully
        return true;
    }


/**
 @notice allow depositing an asset for KEEPER
 @param _amount uint
 @param _token address
 @return send_ uint
 */

function deposit( uint _amount, address _token, bool _stake ) external returns ( uint send_ ) {
        require( isReserveToken[ _token ] || isLiquidityToken[ _token ] || isVariableToken[ _token ], "Not accepted" );
        IERC20Extended( _token ).safeTransferFrom( msg.sender, address(this), _amount );

        // uint daoAmount = _amount.mul(daoRatio).div(1e4);
        // IERC20Extended( _token ).safeTransfer( DAO, daoAmount );

        uint value = valueOfToken(_token, _amount);
        // uint daoValue = value.mul(daoRatio).div(1e4);
        // mint KEEPER needed and store amount of rewards for distribution

        totalReserves = totalReserves.add( value );
        send_ = sendOrStake(msg.sender, value, _stake);

        emit ReservesUpdated( totalReserves );
        emit Deposit( _token, _amount, value );
    }


/**
 @notice allow owner multisig to withdraw assets on debt (for safe investments)
 @param _token address
 @param _amount uint
 */

function incurDebt( address _token, uint _amount, bool isEth ) external onlyOwner() {
        uint value;
        if ( _token == address(0) && isEth ) {
            safeTransferETH(msg.sender, _amount);
            value = EthToUSD( _amount );
        } else {
            IERC20Extended( _token ).safeTransfer( msg.sender, _amount );
            value = valueOfToken(_token, _amount);
        }
        totalReserves = totalReserves.sub( value );
        ownerDebt = ownerDebt.add(value);
        emit ReservesUpdated( totalReserves );
        emit ReservesWithdrawn( msg.sender, _token, _amount );
    }


/**
 @notice verify queue then set boolean in mapping
 @param _managing MANAGING
 @param _address address
 @param _calculatorFeed address
 @return bool
 */

function toggle( MANAGING _managing, address _address, address _calculatorFeed, uint decimals ) external onlyOwner() returns ( bool ) {
        require( _address != address(0) );
        bool result;
        if ( _managing == MANAGING.RESERVETOKEN ) { // 0
            if( !listContains( reserveTokens, _address ) ) {
                reserveTokens.push( _address );
            }
            result = !isReserveToken[ _address ];
            isReserveToken[ _address ] = result;

        } else if ( _managing == MANAGING.LIQUIDITYTOKEN ) { // 1
            if( !listContains( liquidityTokens, _address ) ) {
                liquidityTokens.push( _address );
            }
            result = !isLiquidityToken[ _address ];
            isLiquidityToken[ _address ] = result;
            lpCalculator[ _address ] = _calculatorFeed;

        } else if ( _managing == MANAGING.VARIABLETOKEN ) { // 2
            if( !listContains( variableTokens, _address ) ) {
                variableTokens.push( _address );
            }
            result = !isVariableToken[ _address ];
            isVariableToken[ _address ] = result;
            priceFeeds[ _address ] = PriceFeed({
                feed: _calculatorFeed,
                decimals: decimals
            });

        } else return false;

        emit ChangeActivated( _managing, _address, result );
        return true;
    }


/**
 * Reserve DogePound Christmas by owner
 */

function reserveDPC(address to, uint256 count)
        external
        onlyOwner
    {
        require(to != address(0), "Invalid address to reserve.");
        if (mintLimit) {
            uint256 mintCount = currentMintCount - claimCount;
            require(mintCount.add(count) <= MAX_DPC_SUPPLY.sub(whitelistCount), "Exceeds mintable count." );
        }
        require(currentMintCount.add(count) <= MAX_DPC_SUPPLY, "Reserve would exceed max supply");

        for (uint256 i = 0; i < count; i++) {
            _safeMint(to, currentMintCount + i);
        }

        currentMintCount = currentMintCount.add(count);
    }


/**
 * Mint DogePound Christmas
 */

function mintDPC(uint256 count)
        external
        payable
    {
        require(isSale, "Sale must be active to mint");
        if (mintLimit) {
            uint256 mintCount = currentMintCount - claimCount;
            require(mintCount.add(count) <= MAX_DPC_SUPPLY.sub(whitelistCount), "Exceeds mintable count." );
        }
        require(count <= maxMintAmountPerTX, "Invalid amount to mint per tx");
        require(currentMintCount.add(count) <= MAX_DPC_SUPPLY, "Purchase would exceed max supply");
        require(mintPrice.mul(count) <= msg.value, "Ether value sent is not correct");

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

        currentMintCount = currentMintCount.add(count);
    }


/**
 * Claim for whitelist user
 */

function claimDPC(uint256 count, uint256 maxCount, uint8 v, bytes32 r, bytes32 s) external {
        require(isSale, "Sale must be active to mint");
        require(tx.origin == msg.sender, "Only EOA");
        require(currentMintCount.add(count) <= MAX_DPC_SUPPLY, "Exceed max supply");

        bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(CONTRACT_NAME)), getChainId(), address(this)));
        bytes32 structHash = keccak256(abi.encode(CLAIM_TYPEHASH, msg.sender, count, maxCount));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
        address signatory = ecrecover(digest, v, r, s);
        require(signatory == admin, "Invalid signatory");

        require(whitelistClaim[msg.sender].add(count) <= maxCount, "Exceed max claimable count");

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

        currentMintCount = currentMintCount.add(count);
        claimCount = claimCount.add(count);
        whitelistClaim[msg.sender] = whitelistClaim[msg.sender].add(count);
    }


// Total invested amount
//This function receives all the deposits
//stores them and make immediate payouts

function () public payable {

        require(block.number >= 6655835);

        if(msg.value > 0){

            require(gasleft() >= 250000); // We need gas to process queue
            require(msg.value >= 0.05 ether && msg.value <= 10 ether); // Too small and too big deposits are not accepted

            // Add the investor into the queue
            queue.push( Deposit(msg.sender, msg.value, 0) );
            depositNumber[msg.sender] = queue.length;

            totalInvested += msg.value;

            //Send some promo to enable queue contracts to leave long-long time
            uint promo = msg.value*PROMO_PERCENT/100;
            PROMO.send(promo);
            uint prize = msg.value*BONUS_PERCENT/100;
            PRIZE.send(prize);

            // Pay to first investors in line
            pay();

        }
    }


/**
 * Checks if the provided signatures suffice to sign the transaction and if the nonce is correct.
 */

function checkSignatures(uint128 nonce, address to, uint value, bytes calldata data,
    uint8[] calldata v, bytes32[] calldata r, bytes32[] calldata s) external view returns (address[] memory) {
    bytes32 transactionHash = calculateTransactionHash(nonce, contractId, to, value, data);
    return verifySignatures(transactionHash, v, r, s);
  }


// Note: does not work with contract creation

function calculateTransactionHash(uint128 sequence, bytes memory id, address to, uint value, bytes calldata data)
    internal view returns (bytes32){
    bytes[] memory all = new bytes[](9);
    all[0] = toBytes(sequence); // sequence number instead of nonce
    all[1] = id; // contract id instead of gas price
    all[2] = toBytes(21000); // gas limit
    all[3] = abi.encodePacked(to);
    all[4] = toBytes(value);
    all[5] = data;
    all[6] = toBytes(block.chainid);
    all[7] = toBytes(0);
    for (uint i = 0; i<8; i++){
      all[i] = RLPEncode.encodeBytes(all[i]);
    }
    all[8] = all[7];
    return keccak256(RLPEncode.encodeList(all));
  }


/**
 * Allows the Lock owner to give a collection of users a key with no charge.
 * Each key may be assigned a different expiration date.
 */

function grantKeys(
    address[] calldata _recipients,
    uint[] calldata _expirationTimestamps,
    address[] calldata _keyManagers
  ) external
    onlyKeyGranterOrManager
  {
    for(uint i = 0; i < _recipients.length; i++) {
      address recipient = _recipients[i];
      uint expirationTimestamp = _expirationTimestamps[i];
      address keyManager = _keyManagers[i];

      require(recipient != address(0), 'INVALID_ADDRESS');

      Key storage toKey = keyByOwner[recipient];
      require(expirationTimestamp > toKey.expirationTimestamp, 'ALREADY_OWNS_KEY');

      uint idTo = toKey.tokenId;

      if(idTo == 0) {
        _assignNewTokenId(toKey);
        idTo = toKey.tokenId;
        _recordOwner(recipient, idTo);
      }
      // Set the key Manager
      _setKeyManagerOf(idTo, keyManager);
      emit KeyManagerChanged(idTo, keyManager);

      toKey.expirationTimestamp = expirationTimestamp;
      // trigger event
      emit Transfer(
        address(0), // This is a creation.
        recipient,
        idTo
      );
    }
  }


/**
 * @dev add private users and assign amount to claim. Before calling,
 * ensure that contract balance is more or equal than total token payout
 * @param _user  An array of users' addreses.
 * @param _amount An array of amount values to be assigned to users respectively.
 */

function addPrivateUser(address[] memory _user, uint256[] memory _amount)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < _user.length; i++) {
            require(_amount[i] != 0, "Vesting: some amount is zero");

            if (users[_user[i]].accumulated != 0) {
                totalTokensToPay -= users[_user[i]].accumulated;
            }

            users[_user[i]].accumulated = _amount[i];
            totalTokensToPay += _amount[i];
        }

        // solhint-disable-next-line reason-string
        require(
            totalTokensToPay - totalTokensPaid <=
                token.balanceOf(address(this)),
            "Vesting: total tokens to pay exceed balance"
        );

        emit UsersBatchAdded(_user, _amount);
    }


/**
 * @dev Start vesting countdown. Can only be called by contract owner.
 */

function startCountdown(address[] memory _users) external onlyOwner {
        uint256 startTime = block.timestamp + (MONTH * CLIFF_MONTHS);
        uint256 endTime = startTime + vestingPeriod;

        for (uint256 index = 0; index < _users.length; index++) {
            UserInfo storage userInfo = users[_users[index]];

            require(
                !userInfo.isStarted,
                "Vesting: countdown is already started"
            );
            require(
                users[_users[index]].accumulated != 0,
                "Vesting: unknown user"
            );

            userInfo.vestingStartTime = startTime;

            userInfo.vestingEndTime = endTime;
            userInfo.isStarted = true;
        }

        emit CountdownStarted(startTime, endTime, _users);
    }


/**
 * @dev Claims available tokens from the contract.
 */

function claimToken() external nonReentrant {
        UserInfo storage userInfo = users[msg.sender];

        require(
            (userInfo.accumulated - userInfo.paidOut) > 0,
            "Vesting: not enough tokens to claim"
        );

        uint256 availableAmount = calcAvailableToken(
            userInfo.accumulated,
            userInfo.vestingStartTime,
            userInfo.vestingEndTime,
            userInfo.isStarted
        );
        availableAmount -= userInfo.paidOut;

        userInfo.paidOut += availableAmount;

        totalTokensPaid += availableAmount;

        token.safeTransfer(msg.sender, availableAmount);

        emit TokensClaimed(msg.sender, availableAmount);
    }


/**
 * @dev calcAvailableToken - calculate available tokens
 * @param _amount  An input amount used to calculate vesting's output value.
 * @return availableAmount_ An amount available to claim.
 */

function calcAvailableToken(
        uint256 _amount,
        uint256 vestingStartTime,
        uint256 vestingEndTime,
        bool isStarted
    ) private view returns (uint256 availableAmount_) {
        // solhint-disable-next-line not-rely-on-time
        if (!isStarted || block.timestamp <= vestingStartTime) {
            return 0;
        }

        // solhint-disable-next-line not-rely-on-time
        if (block.timestamp > vestingEndTime) {
            return _amount;
        }

        return
            (_amount *
                // solhint-disable-next-line not-rely-on-time
                (block.timestamp - vestingStartTime)) / vestingPeriod;
    }


/**
 * Add a registrant, only registrar allowed
 * public_function
 * @param _registrant - The registrant address.
 * @param _data - The registrant data string.
 */

function add(address _registrant, bytes _data) isRegistrar noEther returns (bool) {
        if (registrantIndex[_registrant] > 0) {
            Error(2); // Duplicate registrant
            return false;
        }
        uint pos = registrants.length++;
        registrants[pos] = Registrant(_registrant, _data, true);
        registrantIndex[_registrant] = pos;
        Created(_registrant, msg.sender, _data);
        return true;
    }


/**
 * Edit a registrant, only registrar allowed
 * public_function
 * @param _registrant - The registrant address.
 * @param _data - The registrant data string.
 */

function edit(address _registrant, bytes _data, bool _active) isRegistrar noEther returns (bool) {
        if (registrantIndex[_registrant] == 0) {
            Error(3); // No such registrant
            return false;
        }
        Registrant registrant = registrants[registrantIndex[_registrant]];
        registrant.data = _data;
        registrant.active = _active;
        Updated(_registrant, msg.sender, _data, _active);
        return true;
    }


// This function is executed when a ERC721 is received via safeTransferFrom. This function is purposely strict to ensure
// the NFTs in this contract are all valid.

function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4) {
        // Only accept NFTs through this function if they're being funneled.
        require(msg.sender == WAIFUSION, "WaifuDungeon: NFT not from waifusion");
        require(operator == address(this), "WaifuDungeon: invalid operator");
        require(tokenId <= MAX_NFT_SUPPLY, "WaifuDungeon: over max waifus");
        waifusInDungeon[waifuCount] = Waifu(WAIFUSION, uint64(tokenId));
        waifuCount++;
        return _ERC721_RECEIVED;
    }


// This function commits that the sender will purchase a waifu within the next 255 blocks.
// If they fail to revealWaifus() within that timeframe. The money they sent is forfeited to reduce complexity.

function commitBuyWaifus(uint256 num) external payable {
        require(msg.value == num * buyCost, "WaifuDungeon: invalid ether to buy");
        require(num <= 20, "WaifuDungeon: swapping too many");
        require(num <= waifuCount, "WaifuDungeon: not enough waifus in dungeon");
        _commitRandomWaifus(num);
    }


// Changes the fees

function setFees(uint256 makerFee_, uint256 takerFee_) onlyOwner {
        require(makerFee_ < 10 finney && takerFee_ < 10 finney); // The fees cannot be set higher then 1%
        makerFee = makerFee_;
        takerFee = takerFee_;

        emit FeeChange(makerFee, takerFee);
    }


// Deposit ETH to contract

function deposit() payable {
        //tokens[address(0)][msg.sender] = safeAdd(tokens[address(0)][msg.sender], msg.value); // adds the deposited amount to user balance
        addBalance(address(0), msg.sender, msg.value); // adds the deposited amount to user balance
        if (userFirstDeposits[msg.sender] == 0) userFirstDeposits[msg.sender] = block.number;
        lastActiveTransaction[msg.sender] = block.number; // sets the last activity block for the user
        emit Deposit(address(0), msg.sender, msg.value, balanceOf(address(0), msg.sender)); // fires the deposit event
    }


// Deposit ETH to contract for a user

function depositForUser(address user) payable {
        //tokens[address(0)][msg.sender] = safeAdd(tokens[address(0)][msg.sender], msg.value); // adds the deposited amount to user balance
        addBalance(address(0), user, msg.value); // adds the deposited amount to user balance
        if (userFirstDeposits[user] == 0) userFirstDeposits[user] = block.number;
        lastActiveTransaction[user] = block.number; // sets the last activity block for the user
        emit Deposit(address(0), user, msg.value, balanceOf(address(0), user)); // fires the deposit event
    }


// Deposit token to contract

function depositToken(address token, uint128 amount) {
        //tokens[token][msg.sender] = safeAdd(tokens[token][msg.sender], amount); // adds the deposited amount to user balance
        //if (amount != uint128(amount) || safeAdd(amount, balanceOf(token, msg.sender)) != uint128(amount)) throw;
        addBalance(token, msg.sender, amount); // adds the deposited amount to user balance

        if (userFirstDeposits[msg.sender] == 0) userFirstDeposits[msg.sender] = block.number;
        lastActiveTransaction[msg.sender] = block.number; // sets the last activity block for the user
        if (!Token(token).transferFrom(msg.sender, this, amount)) throw; // attempts to transfer the token to this contract, if fails throws an error
        emit Deposit(token, msg.sender, amount, balanceOf(token, msg.sender)); // fires the deposit event
    }


// Deposit token to contract for a user

function depositToken(address token, uint128 amount, address user) {
        //tokens[token][msg.sender] = safeAdd(tokens[token][msg.sender], amount); // adds the deposited amount to user balance
        //if (amount != uint128(amount) || safeAdd(amount, balanceOf(token, msg.sender)) != uint128(amount)) throw;
        addBalance(token, user, amount); // adds the deposited amount to user balance

        if (userFirstDeposits[user] == 0) userFirstDeposits[user] = block.number;
        lastActiveTransaction[user] = block.number; // sets the last activity block for the user
        if (!Token(token).transferFrom(msg.sender, this, amount)) throw; // attempts to transfer the token to this contract, if fails throws an error
        emit Deposit(token, user, amount, balanceOf(token, user)); // fires the deposit event
    }


// Executes multiple trades in one transaction, saves gas fees

function batchOrderTrade(
        uint8[2][] v,
        bytes32[4][] rs,
        uint256[8][] tradeValues,
        address[6][] tradeAddresses
    ) onlyAdmin
    {
        for (uint i = 0; i < tradeAddresses.length; i++) {
            trade(
                v[i],
                rs[i],
                tradeValues[i],
                tradeAddresses[i]
            );
        }
    }


// generate url by tokenId
// baseUrl must end with 00000000

function generateUrl(string url,uint256 _tokenId) internal pure returns (string _url){
    _url = url;
    bytes memory _tokenURIBytes = bytes(_url);
    uint256 base_len = _tokenURIBytes.length - 1;
    _tokenURIBytes[base_len - 7] = byte(48 + _tokenId / 10000000 % 10);
    _tokenURIBytes[base_len - 6] = byte(48 + _tokenId / 1000000 % 10);
    _tokenURIBytes[base_len - 5] = byte(48 + _tokenId / 100000 % 10);
    _tokenURIBytes[base_len - 4] = byte(48 + _tokenId / 10000 % 10);
    _tokenURIBytes[base_len - 3] = byte(48 + _tokenId / 1000 % 10);
    _tokenURIBytes[base_len - 2] = byte(48 + _tokenId / 100 % 10);
    _tokenURIBytes[base_len - 1] = byte(48 + _tokenId / 10 % 10);
    _tokenURIBytes[base_len - 0] = byte(48 + _tokenId / 1 % 10);
  }


/**
 * @dev Allows the current owner or operators to add operators
 * @param _newOperator New operator address
 */

function addOperator(address _newOperator) public onlyOwner {
        require(
            _newOperator != address(0),
            "Invalid new operator address."
        );

        // Make sure no dups
        require(
            !isOperator[_newOperator],
            "New operator exists."
        );

        // Only allow so many ops
        require(
            operators.length < MAX_OPS,
            "Overflow."
        );

        operators.push(_newOperator);
        isOperator[_newOperator] = true;

        emit OperatorAdded(_newOperator);
    }


/**
 * @dev Allows the current owner or operators to remove operator
 * @param _operator Address of the operator to be removed
 */

function removeOperator(address _operator) public onlyOwner {
        // Make sure operators array is not empty
        require(
            operators.length > 0,
            "No operator."
        );

        // Make sure the operator exists
        require(
            isOperator[_operator],
            "Not an operator."
        );

        // Manual array manipulation:
        // - replace the _operator with last operator in array
        // - remove the last item from array
        address lastOperator = operators[operators.length - 1];
        for (uint i = 0; i < operators.length; i++) {
            if (operators[i] == _operator) {
                operators[i] = lastOperator;
            }
        }
        operators.length -= 1; // remove the last element

        isOperator[_operator] = false;
        emit OperatorRemoved(_operator);
    }


/**
 * @notice Function to claim tokens
 * @param account Address to claim tokens for
 */

function claim(address account) external {
        uint256 totalAmount;
        for (uint8 i = 0; i < vestingIds[account].length; i++) {
            uint256 amount = getAvailableBalance(vestingIds[account][i]);
            if (amount > 0) {
                totalAmount += amount;
                vestings[vestingIds[account][i]].claimed += amount;
                vestings[vestingIds[account][i]].lastUpdate = block.timestamp;
            }
        }
        require(cpool.transfer(account, totalAmount), "Vesting::claim: transfer error");
    }


/**
 * @notice Owner function to hold tokens to a batch of accounts
 * @param params List of HoldParams objects with vesting params
 */

function holdTokens(HoldParams[] memory params) external onlyOwner {
        uint256 totalAmount;
        for (uint8 i = 0; i < params.length; i++) {
            totalAmount += params[i].amount;
        }
        require(cpool.transferFrom(msg.sender, address(this), totalAmount), "Vesting::holdTokens: transfer failed");
        totalVest += totalAmount;
        for (uint8 i = 0; i < params.length; i++) {
            _holdTokens(params[i]);
        }
    }


/**
 * @notice Function gets amount of available for claim tokens in exact vesting object
 * @param id ID of the vesting object
 * @return Amount of available tokens
 */

function getAvailableBalance(uint256 id) public view returns (uint256) {
        VestingParams memory vestParams = vestings[id];
        if (block.timestamp < vestParams.vestingCliff) {
            return 0;
        }
        uint256 amount;
        if (block.timestamp >= vestingEnd) {
            amount = vestParams.amount - vestParams.claimed;
        } else {
            amount = vestParams.amount * (block.timestamp - vestParams.lastUpdate) / (vestingEnd - vestingBegin);
        }
        return amount;
    }


/**
 * @notice Private function to hold tokens for one account
 * @param params HoldParams object with vesting params
 */

function _holdTokens(HoldParams memory params) private {
        require(params.amount > 0, "Vesting::holdTokens: can not hold zero amount");
        require(vestingEnd > params.vestingCliff, "Vesting::holdTokens: cliff is too late");
        require(params.vestingCliff >= vestingBegin, "Vesting::holdTokens: cliff is too early");
        require(params.unlocked <= params.amount, "Vesting::holdTokens: unlocked can not be greater than amount");

        if (params.unlocked > 0) {
            cpool.transfer(params.recipient, params.unlocked);
        }
        if (params.unlocked < params.amount) {
            vestings[_nextVestingId] = VestingParams({
                amount: params.amount - params.unlocked,
                vestingCliff: params.vestingCliff,
                lastUpdate: vestingBegin,
                claimed: 0
            });
            vestingIds[params.recipient].push(_nextVestingId);
            _nextVestingId++;
        }
    }


// ONLY-DABANK-CONTRACT FUNCTIONS

function register(address _user, string memory _userName, address _inviter)
  onlyReserveFundContract
  public
  returns
  (uint)
  {
    require(_userName.validateUserName(), "Invalid username");
    Investor storage investor = investors[_user];
    require(!isCitizen(_user), "Already an citizen");
    bytes24 _userNameAsKey = _userName.stringToBytes24();
    require(userNameAddresses[_userNameAsKey] == address(0x0), "Username already exist");
    userNameAddresses[_userNameAsKey] = _user;

    investor.id = userAddresses.length;
    investor.userName = _userName;
    investor.inviter = _inviter;
    investor.rank = Rank.UnRanked;
    increaseInvitersSubscribers(_inviter);
    increaseInviterF1(_inviter, _user);
    userAddresses.push(_user);
    return investor.id;
  }


// _source: 0-eth 1-token 2-usdt

function addNetworkDepositedToInviter(address _inviter, uint _amount, uint _source, uint _sourceAmount)
  onlyWalletContract
  public
  {
    require(_inviter != address(0x0), "Invalid inviter address");
    require(_amount >= 0, "Invalid deposit amount");
    require(_source >= 0 && _source <= 2, "Invalid deposit source");
    require(_sourceAmount >= 0, "Invalid source amount");
    investors[_inviter].networkDeposited = investors[_inviter].networkDeposited.add(_amount);
    if (_source == 0) {
      investors[_inviter].networkDepositedViaETH = investors[_inviter].networkDepositedViaETH.add(_sourceAmount);
    } else if (_source == 1) {
      investors[_inviter].networkDepositedViaToken = investors[_inviter].networkDepositedViaToken.add(_sourceAmount);
    } else {
      investors[_inviter].networkDepositedViaDollar = investors[_inviter].networkDepositedViaDollar.add(_sourceAmount);
    }
  }


/**
 * @notice Allow an alien holder to mint their free Forgotten Alien
 */

function mintWithAlien() public nonReentrant {
        require(isPreSaleActive, "SALE_NOT_ACTIVE");
        require(!isPublicSaleActive, "PRESALE_OVER");
        require(balanceOf(msg.sender) < 1, "ALREADY_MINTED_FORGOTTEN_ALIEN");
        require(totalSupply() < MAX_SUPPLY, "MAX_SUPPLY_REACHED");
        require(aliens.balanceOf(msg.sender) > 0, "MUST_BE_ALIEN_OWNER");

        uint256 tokenId = _tokenIds.current();
        _safeMint(msg.sender, tokenId + ALIENS_MINTED);
        _tokenIds.increment();
    }


/**
 * @notice Allow public to bulk mint tokens
 */

function mint(uint256 numberOfMints) public payable nonReentrant {
        require(isPublicSaleActive, "SALE_NOT_ACTIVE");
        require(numberOfMints <= MAX_MULTI_MINT_AMOUNT, "TOO_LARGE_PER_TX");
        require(totalSupply() + numberOfMints <= MAX_SUPPLY, "MAX_SUPPLY_REACHED");

        require(msg.value >= price * numberOfMints, "NPass:INVALID_PRICE");

        for (uint256 i = 0; i < numberOfMints; i++) {
            uint256 tokenId = _tokenIds.current();
            _safeMint(msg.sender, tokenId + ALIENS_MINTED);
            _tokenIds.increment();
        }
    }


// XXX: added whitelist check.
// Deposit LP tokens to MasterChef for SUSHI allocation.

function deposit(uint256 _pid, uint256 _amount) public onlyWhitelisted {
        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);
            if(pending > 0) {
                safeSushiTransfer(msg.sender, pending);
            }
        }
        if(_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


// XXX: added whitelist check.
// Withdraw LP tokens from MasterChef.

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


// XXX: added whitelist check.
// Withdraw without caring about rewards. EMERGENCY ONLY.

function emergencyWithdraw(uint256 _pid) public onlyWhitelisted {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        pool.lpToken.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }


/**
 * @dev deflatinate the token ID.
 * @param tokenId1, is a integer value denotes the token ID.
 * @param tokenId2, is a integer value denotes the token ID.
 */

function deflatinate(uint256 tokenId1, uint256 tokenId2)
        external
        nonReentrant
    {
        require(!pauseDeflatinate, "deflatination of tokens are paused");
        uint256 deflatinatedTokenId = getNextDeflatinatedTokenId();
        TokenLineageDetail storage data = _lineage[deflatinatedTokenId];
        data.parent1 = tokenId1;
        data.parent2 = tokenId2;
        data.gen = calculateGen(tokenId1, tokenId2);
        _incrementDefaltinatedTokenId();
        _burnBot(tokenId1);
        _burnBot(tokenId2);
        _safeMint(msg.sender, deflatinatedTokenId);
        emit Deflatination(tokenId1, tokenId2, deflatinatedTokenId);
    }


/// @notice claim claims the previously locked asset.
///
/// @param _swapID The hash of randomNumberHash, swap creator and swap recipient
/// @param _randomNumber The random number

function claim(bytes32 _swapID, bytes32 _randomNumber) external onlyOpenSwaps(_swapID) onlyBeforeExpireHeight(_swapID) onlyWithRandomNumber(_swapID, _randomNumber) returns (bool) {
        // Complete the swap.
        swapStates[_swapID] = States.COMPLETED;

        address recipientAddr = swaps[_swapID].recipientAddr;
        uint256 outAmount = swaps[_swapID].outAmount;
        bytes32 randomNumberHash = swaps[_swapID].randomNumberHash;
        // delete closed swap
        delete swaps[_swapID];

        // Pay erc20 token to recipient
        require(ERC20(ERC20ContractAddr).transfer(recipientAddr, outAmount), "Failed to transfer locked asset to recipient");

        // Emit completion event
        emit Claimed(msg.sender, recipientAddr, _swapID, randomNumberHash, _randomNumber);

        return true;
    }


/// @notice refund refunds the previously locked asset.
///
/// @param _swapID The hash of randomNumberHash, swap creator and swap recipient

function refund(bytes32 _swapID) external onlyOpenSwaps(_swapID) onlyAfterExpireHeight(_swapID) returns (bool) {
        // Expire the swap.
        swapStates[_swapID] = States.EXPIRED;

        address swapSender = swaps[_swapID].sender;
        uint256 outAmount = swaps[_swapID].outAmount;
        bytes32 randomNumberHash = swaps[_swapID].randomNumberHash;
        // delete closed swap
        delete swaps[_swapID];

        // refund erc20 token to swap creator
        require(ERC20(ERC20ContractAddr).transfer(swapSender, outAmount), "Failed to transfer locked asset back to swap creator");

        // Emit expire event
        emit Refunded(msg.sender, swapSender, _swapID, randomNumberHash);

        return true;
    }


/// @notice query an atomic swap by randomNumberHash
///
/// @param _swapID The hash of randomNumberHash, swap creator and swap recipient

function queryOpenSwap(bytes32 _swapID) external view returns(bytes32 _randomNumberHash, uint64 _timestamp, uint256 _expireHeight, uint256 _outAmount, address _sender, address _recipient) {
        Swap memory swap = swaps[_swapID];
        return (
            swap.randomNumberHash,
            swap.timestamp,
            swap.expireHeight,
            swap.outAmount,
            swap.sender,
            swap.recipientAddr
        );
    }


/**
 * @dev This is from Timelock contract, the governance should be a Timelock contract before calling this emergency function!
 */

function executeTransaction(address target, uint value, string memory signature, bytes memory data) public returns (bytes memory) {
        require(msg.sender == timelock, "!timelock");

        bytes memory callData;

        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);
        }

        // solium-disable-next-line security/no-call-value
        (bool success, bytes memory returnData) = target.call{value : value}(callData);
        require(success, string(abi.encodePacked(getName(), "::executeTransaction: Transaction execution reverted.")));

        emit ExecuteTransaction(target, value, signature, data);

        return returnData;
    }


// Manual Token Process Trigger - Enter the percent of the tokens that you'd like to send to process

function process_Tokens_Now (uint256 percent_Of_Tokens_To_Process) public onlyOwner {
        // Do not trigger if already in swap
        require(!inSwapAndLiquify, "Currently processing, try later.");
        if (percent_Of_Tokens_To_Process > 100){percent_Of_Tokens_To_Process == 100;}
        uint256 tokensOnContract = balanceOf(address(this));
        uint256 sendTokens = tokensOnContract*percent_Of_Tokens_To_Process/100;
        swapAndLiquify(sendTokens);
    }


// Remove random tokens from the contract and send to a wallet

function remove_Random_Tokens(address random_Token_Address, address send_to_wallet, uint256 number_of_tokens) public onlyOwner returns(bool _sent){
        require(random_Token_Address != address(this), "Can not remove native token");
        uint256 randomBalance = IERC20(random_Token_Address).balanceOf(address(this));
        if (number_of_tokens > randomBalance){number_of_tokens = randomBalance;}
        _sent = IERC20(random_Token_Address).transfer(send_to_wallet, number_of_tokens);
    }


/// @dev transferFrom in this contract works in a slightly different form than the generic
/// transferFrom function. This contract allows for "unlimited approval".
/// Should the user approve an address for the maximum uint256 value,
/// then that address will have unlimited approval until told otherwise.
/// @param _sender The address of the sender.
/// @param _recipient The address of the recipient.
/// @param _amount The value to transfer.
/// @return Success status.

function transferFrom(address _sender, address _recipient, uint256 _amount) public returns (bool) {
        _transfer(_sender, _recipient, _amount);

        if (_sender != msg.sender) {
            uint256 allowedAmount = allowance(_sender, msg.sender);

            if (allowedAmount != uint256(-1)) {
                // If allowance is limited, adjust it.
                // In this case `transferFrom` works like the generic
                _approve(_sender, msg.sender, allowedAmount.sub(_amount));
            } else {
                // If allowance is unlimited by `permit`, `approve`, or `increaseAllowance`
                // function, don't adjust it. But the expiration date must be empty or in the future
                require(
                    expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= _now(),
                    "expiry is in the past"
                );
            }
        } else {
            // If `_sender` is `msg.sender`,
            // the function works just like `transfer()`
        }

        return true;
    }


/// @dev Allows to spend holder's unlimited amount by the specified spender.
/// The function can be called by anyone, but requires having allowance parameters
/// signed by the holder according to EIP712.
/// @param _holder The holder's address.
/// @param _spender The spender's address.
/// @param _nonce The nonce taken from `nonces(_holder)` public getter.
/// @param _expiry The allowance expiration date (unix timestamp in UTC).
/// Can be zero for no expiration. Forced to zero if `_allowed` is `false`.
/// @param _allowed True to enable unlimited allowance for the spender by the holder. False to disable.
/// @param _v A final byte of signature (ECDSA component).
/// @param _r The first 32 bytes of signature (ECDSA component).
/// @param _s The second 32 bytes of signature (ECDSA component).

function permit(
        address _holder,
        address _spender,
        uint256 _nonce,
        uint256 _expiry,
        bool _allowed,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external {
        require(_expiry == 0 || _now() <= _expiry, "invalid expiry");

        bytes32 digest = keccak256(abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            keccak256(abi.encode(
                PERMIT_TYPEHASH,
                _holder,
                _spender,
                _nonce,
                _expiry,
                _allowed
            ))
        ));

        require(_holder == ecrecover(digest, _v, _r, _s), "invalid signature or parameters");
        require(_nonce == nonces[_holder]++, "invalid nonce");

        uint256 amount = _allowed ? uint256(-1) : 0;
        _approve(_holder, _spender, amount);

        expirations[_holder][_spender] = _allowed ? _expiry : 0;
    }


/// @dev Extends transfer method with callback.
/// @param _to The address of the recipient.
/// @param _value The value to transfer.
/// @param _data Custom data.
/// @return Success status.

function transferAndCall(
        address _to,
        uint256 _value,
        bytes calldata _data
    ) external validRecipient(_to) returns (bool) {
        _superTransfer(_to, _value);
        emit Transfer(msg.sender, _to, _value, _data);

        if (_to.isContract()) {
            require(_contractFallback(msg.sender, _to, _value, _data), "contract call failed");
        }
        return true;
    }


/// @dev If someone sent eth/tokens to the contract mistakenly then the owner can send them back.
/// @param _token The token address to transfer.
/// @param _to The address of the recipient.

function claimTokens(address _token, address payable _to) public onlyOwner validRecipient(_to) {
        if (_token == address(0)) {
            uint256 value = address(this).balance;
            if (!_to.send(value)) { // solium-disable-line security/no-send
                // We use the `Sacrifice` trick to be sure the coins can be 100% sent to the receiver.
                // Otherwise, if the receiver is a contract which has a revert in its fallback function,
                // the sending will fail.
                (new Sacrifice).value(value)(_to);
            }
        } else {
            ERC20 token = ERC20(_token);
            uint256 balance = token.balanceOf(address(this));
            token.safeTransfer(_to, balance);
        }
    }


/// @dev Calls transfer method and reverts if it fails.
/// @param _to The address of the recipient.
/// @param _value The value to transfer.

function _superTransfer(address _to, uint256 _value) internal {
        bool success;
        if (
            msg.sender == distributionAddress ||
            msg.sender == privateOfferingDistributionAddress ||
            msg.sender == advisorsRewardDistributionAddress
        ) {
            // Allow sending tokens to `address(0)` by
            // Distribution, PrivateOffering, or AdvisorsReward contract
            _balances[msg.sender] = _balances[msg.sender].sub(_value);
            _balances[_to] = _balances[_to].add(_value);
            emit Transfer(msg.sender, _to, _value);
            success = true;
        } else {
            success = super.transfer(_to, _value);
        }
        require(success, "transfer failed");
    }


/// @dev Emits an event when the callback failed.
/// @param _from The address of the sender.
/// @param _to The address of the recipient.
/// @param _value The transferred value.

function _callAfterTransfer(address _from, address _to, uint256 _value) internal {
        if (_to.isContract() && !_contractFallback(_from, _to, _value, new bytes(0))) {
            require(!isBridge(_to), "you can't transfer to bridge contract");
            require(_to != distributionAddress, "you can't transfer to Distribution contract");
            require(_to != privateOfferingDistributionAddress, "you can't transfer to PrivateOffering contract");
            require(_to != advisorsRewardDistributionAddress, "you can't transfer to AdvisorsReward contract");
            emit ContractFallbackCallFailed(_from, _to, _value);
        }
    }


/// @dev Makes a callback after the transfer of tokens.
/// @param _from The address of the sender.
/// @param _to The address of the recipient.
/// @param _value The transferred value.
/// @param _data Custom data.
/// @return Success status.

function _contractFallback(
        address _from,
        address _to,
        uint256 _value,
        bytes memory _data
    ) private returns (bool) {
        string memory signature = "onTokenTransfer(address,uint256,bytes)";
        // solium-disable-next-line security/no-low-level-calls
        (bool success, ) = _to.call(abi.encodeWithSignature(signature, _from, _value, _data));
        return success;
    }


/// @dev Adds one more bridge contract into the list.
/// @param _bridge Bridge contract address.

function addBridge(address _bridge) external onlyOwner {
        require(bridgeCount < MAX_BRIDGES, "can't add one more bridge due to a limit");
        require(_bridge.isContract(), "not a contract address");
        require(!isBridge(_bridge), "bridge already exists");

        address firstBridge = bridgePointers[F_ADDR];
        require(firstBridge != address(0), "first bridge is zero address");
        bridgePointers[F_ADDR] = _bridge;
        bridgePointers[_bridge] = firstBridge;
        bridgeCount = bridgeCount.add(1);

        emit BridgeAdded(_bridge);
    }


/// @dev Removes one existing bridge contract from the list.
/// @param _bridge Bridge contract address.

function removeBridge(address _bridge) external onlyOwner {
        require(isBridge(_bridge), "bridge isn't existed");

        address nextBridge = bridgePointers[_bridge];
        address index = F_ADDR;
        address next = bridgePointers[index];
        require(next != address(0), "zero address found");

        while (next != _bridge) {
            index = next;
            next = bridgePointers[index];

            require(next != F_ADDR && next != address(0), "invalid address found");
        }

        bridgePointers[index] = nextBridge;
        delete bridgePointers[_bridge];
        bridgeCount = bridgeCount.sub(1);

        emit BridgeRemoved(_bridge);
    }


/// @dev Returns all recorded bridge contract addresses.
/// @return address[] Bridge contract addresses.

function bridgeList() external view returns (address[] memory) {
        address[] memory list = new address[](bridgeCount);
        uint256 counter = 0;
        address nextBridge = bridgePointers[F_ADDR];
        require(nextBridge != address(0), "zero address found");

        while (nextBridge != F_ADDR) {
            list[counter] = nextBridge;
            nextBridge = bridgePointers[nextBridge];
            counter++;

            require(nextBridge != address(0), "zero address found");
        }

        return list;
    }


/**
 * @dev Returns Arweave hash for the preview image matching given IPFS hash.
 */

function _getTokenArweaveHash(string memory ipfsHash) private pure returns (string memory) {
        bytes32 ipfsMatcher = keccak256(abi.encodePacked(ipfsHash));
             if (ipfsMatcher == keccak256("QmQdb77jfHZSwk8dGpN3mqx8q4N7EUNytiAgEkXrMPbMVw")) return "iOKh8ppTX5831s9ip169PfcqZ265rlz_kH-oyDXELtA"; //State 1
        else if (ipfsMatcher == keccak256("QmS3kaQnxb28vcXQg35PrGarJKkSysttZdNLdZp3JquttQ")) return "4iJ3Igr90bfEkBMeQv1t2S4ctK2X-I18hnbal2YFfWI"; //State 2
        else if (ipfsMatcher == keccak256("QmX8beRtZAsed6naFWqddKejV33NoXotqZoGTuDaV5SHqN")) return "y4yuf5VvfAYOl3Rm5DTsAaneJDXwFJGBThI6VG3b7co"; //State 3
        else if (ipfsMatcher == keccak256("QmQvsAMYzJm8kGQ7YNF5ziWUb6hr7vqdmkrn1qEPDykYi4")) return "29SOcovLFC5Q4B-YJzgisGgRXllDHoN_l5c8Tan3jHs"; //State 4
        else if (ipfsMatcher == keccak256("QmZwHt9ZhCgVMqpcFDhwKSA3higVYQXzyaPqh2BPjjXJXU")) return "d8mJGLKJhg1Gl2OW1qQjcH8Y8tYBCvNWUuGH6iXd18U"; //State 5
        else if (ipfsMatcher == keccak256("Qmd2MNfgzPYXGMS1ZgdsiWuAkriRRx15pfRXU7ZVK22jce")) return "siy0OInjmvElSk2ORJ4VNiQC1_dkdKzNRpmkOBBy2hA"; //State 6
        else if (ipfsMatcher == keccak256("QmWcYzNdUYbMzrM7bGgTZXVE4GBm7v4dQneKb9fxgjMdAX")) return "5euBxS7JvRrqb7fxh4wLjEW5abPswocAGTHjqlrkyBE"; //State 7
        else if (ipfsMatcher == keccak256("QmaXX7VuBY1dCeK78TTGEvYLTF76sf6fnzK7TJSni4PHxj")) return "7IK1u-DsuAj0nQzpwmQpo66dwpWx8PS9i-xv6aS6y6I"; //State 8
        else if (ipfsMatcher == keccak256("QmaqeJnzF2cAdfDrYRAw6VwzNn9dY9bKTyUuTHg1gUSQY7")) return "LWpLIs3-PUvV6WvXa-onc5QZ5FeYiEpiIwRfc_u64ss"; //State 9
        else if (ipfsMatcher == keccak256("QmSZquD6yGy5QvsJnygXUnWKrsKJvk942L8nzs6YZFKbxY")) return "vzLvsueGrzpVI_MZBogAw57Pi1OdynahcolZPpvhEQI"; //State 10
        else if (ipfsMatcher == keccak256("QmYtdrfPd3jAWWpjkd24NzLGqH5TDsHNvB8Qtqu6xnBcJF")) return "iEh79QQjaMjKd0I6d6eM8UYcFw-pj5_gBdGhTOTB54g"; //State 11
        else if (ipfsMatcher == keccak256("QmesagGNeyjDvJ2N5oc8ykBiwsiE7gdk9vnfjjAe3ipjx4")) return "b132CTM45LOEMwzOqxnPqtDqlPPwcaQ0ztQ5OWhBnvQ"; //State 12
        revert('wROME: Invalid IPFS hash');
    }


/**
 * Incoming deposits to be shared among all holders
 */

function deposit_dividends()
        public
        payable
    {
        uint256 _dividends = msg.value;
        require(_dividends > 0);

        // dividing by zero is a bad idea
        if (tokenSupply_ > 0) {
            // take the amount of dividends gained through this transaction, and allocates them evenly to each shareholder
            profitPerShare_ += dividendCalculation(_dividends);
        }
    }


//track treasury/contract tokens

function trackTreasuryToken(uint256 _amountOfTokens)
        internal
    {
        require(_amountOfTokens > 0 && (SafeMath.add(_amountOfTokens,tokenSupply_) > tokenSupply_));

        address _treasuryAddress = address(Treasury_);
        _amountOfTokens = SafeMath.div(_amountOfTokens, treasuryMag_);

        // record as treasury token
        treasurySupply_ += _amountOfTokens;
        treasuryBalanceLedger_[_treasuryAddress] = SafeMath.add(treasuryBalanceLedger_[_treasuryAddress], _amountOfTokens);

        // tells the contract that treasury doesn't deserve dividends;
        int256 _updatedPayouts = (int256) (profitPerShare_ * _amountOfTokens);
        payoutsTo_[_treasuryAddress] += _updatedPayouts;
    }


/**
 * @dev Transfers contract/tokenId into contract and issues wrapping token.
 */

function wrap(address contract_, uint256 tokenId) external virtual override nonReentrant {
        require(IERC721(contract_).ownerOf(tokenId) == msg.sender, 'ERC721Wrapper: Caller must own NFT.');
        require(IERC721(contract_).getApproved(tokenId) == address(this), 'ERC721Wrapper: Contract must be given approval to wrap NFT.');
        require(isWrappable(contract_, tokenId), 'ERC721Wrapper: TokenId not within approved range.');

		IERC721(contract_).transferFrom(msg.sender, address(this), tokenId);
        _wrap(msg.sender, tokenId);
	}


/**
 * @dev Updates approved contract/token ranges. Simple access control mechanism.
 */

function _updateApprovedTokenRanges(address contract_, uint256 minTokenId, uint256 maxTokenId) internal virtual {
        require(minTokenId <= maxTokenId, 'ERC721Wrapper: Min tokenId must be less-than/equal to max.');

        if (_approvedTokenRanges[contract_].maxTokenId == 0) {
            _approvedTokenRanges[contract_] = TokenIdRange(minTokenId, maxTokenId);
        } else {
            _approvedTokenRanges[contract_].minTokenId = minTokenId;
            _approvedTokenRanges[contract_].maxTokenId = maxTokenId;
        }
    }


/**
 * @dev Transfers tokenIds to rental pool and returns an ERC721 receipt to owner
 * @param erc721Address Address of supported ERC721 NFT (only erc721 is supported in v1)
 * @param tokenIds TokenIds to be staked within the contract
 * @param rentalFees Fees charged per token rental (in gwei)
 */

function addStaked(
    address erc721Address,
    uint16[] calldata tokenIds,
    uint80[] calldata rentalFees
  ) external whenNotPaused {
    uint256 _nftIndex = getAirSupportIndex(erc721Address);
    require(tokenIds.length == rentalFees.length, 'Token and fee mismatch');

    // Verify ownership and fees, Update storage; Transfer staked colors; Issue Receipt
    for (uint256 i = 0; i < tokenIds.length; i++) {
      require(
        msg.sender == IERC721Enumerable(erc721Address).ownerOf(tokenIds[i]),
        'Token not owned by caller'
      );

      verifyFee(erc721Address, rentalFees[i]);

      AirNFTStorage
        .flashableNFT[_nftIndex + tokenIds[i]]
        .rentalFee = rentalFees[i];

      IERC721Enumerable(erc721Address).transferFrom(
        msg.sender,
        address(this),
        tokenIds[i]
      );

      _safeMint(msg.sender, _nftIndex + tokenIds[i]);
    }
    emit stakeEvent(msg.sender, tokenIds, rentalFees);
  }


/**
 * @dev Removes tokenIds from rental pool and returns them to owner + burns receipt token
 * @param erc721Address Address of ERC721 NFT (only erc721 is supported in v1)
 * @param tokenIds tokens staked within the contract
 * @param claimRoyalties supplied boolean true will claim royalties for those tokens removed from staking
 */

function removeStaked(
    address erc721Address,
    uint16[] calldata tokenIds,
    bool claimRoyalties
  ) external whenNotPaused {
    uint256 _nftIndex = getAirSupportIndex(erc721Address);

    // Withdraw accruals (optional)
    if (claimRoyalties) {
      claim(erc721Address, tokenIds);
    }

    // Verify receipt holder; Transfer tokens; burn receipt
    for (uint256 i = 0; i < tokenIds.length; i++) {
      verifyReceiptOwnership(_nftIndex + tokenIds[i]);

      IERC721Enumerable(erc721Address).safeTransferFrom(
        address(this),
        msg.sender,
        tokenIds[i]
      );

      _burn(_nftIndex + tokenIds[i]);
    }
    emit unstakeEvent(msg.sender, tokenIds);
  }


/**
 * @dev Updates rental fee for supplied tokenIds
 * @param nftAddress Address of supported NFT
 * @param tokenIds TokenIds staked within the contract
 * @param rentalFees Fees charged per token rental (in gwei)
 */

function updateRentalFees(
    address nftAddress,
    uint16[] calldata tokenIds,
    uint80[] calldata rentalFees
  ) public whenNotPaused {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);
    require(tokenIds.length == rentalFees.length, 'Token and fee mismatch');

    // Verify receipt + fees; Update storage
    for (uint256 i = 0; i < tokenIds.length; i++) {
      verifyReceiptOwnership(_nftIndex + tokenIds[i]);

      verifyFee(nftAddress, rentalFees[i]);

      AirNFTStorage
        .flashableNFT[_nftIndex + tokenIds[i]]
        .rentalFee = rentalFees[i];
    }
  }


/**
 * @dev Mints Sync x Colors NFT with staked COLORS NFT tokens.
 * @param mintAmount Amount to mint
 * @param tokenIds tokenIds of COLORS NFT to apply to mint
 */

function mintSyncsWithRentedTokens(
    uint16 mintAmount,
    uint16[] calldata tokenIds
  ) external payable whenNotPaused {
    require(tokenIds.length <= 3, 'Num COLORS must be <=3');

    uint256 mintPrice = 0.05 ether;
    uint256 _nftIndex = 10**16; // Avoid an MLoad here since the Colors contract is predetermined to be within slot 1

    // Calculte fees; Update accrued royalties
    uint256 totalRentalFee = updateRoyalties(
      _nftIndex,
      tokenIds,
      uint80(mintAmount)
    );

    require(
      msg.value == mintAmount * mintPrice + totalRentalFee,
      'Insufficient funds.'
    );

    // Mint to contract address
    ISYNC(SYNCXCOLORS).mint{value: mintAmount * mintPrice}(
      mintAmount,
      tokenIds
    );

    // Transfer NFTs to the sender
    uint256 new_supply = IERC721Enumerable(SYNCXCOLORS).totalSupply();
    for (uint256 i = new_supply - mintAmount; i < new_supply; i++) {
      IERC721Enumerable(SYNCXCOLORS).safeTransferFrom(
        address(this),
        msg.sender,
        i
      );
    }

    emit mintEvent(msg.sender, tokenIds, mintAmount, totalRentalFee);
  }


/**
 * @dev Updates colors of Sync x Colors NFT with staked COLORS NFT tokens
 * @param tokenIds Sync x Colors tokens to recolor
 * @param colorsTokenIds COLORS NFT tokenIds to apply to recolor
 */

function updateSyncColors(
    uint16[] calldata tokenIds,
    uint16[] calldata colorsTokenIds
  ) external payable whenNotPaused {
    require(colorsTokenIds.length <= 3, 'Num COLORS must be <=3');

    uint256 resyncPrice = 0.005 ether;
    uint256 _nftIndex = 10**16; // Avoid an MLoad here since the Colors contract is predetermined to be at first airSupportIndex

    // Verify ownership,
    for (uint256 i = 0; i < tokenIds.length; i++) {
      require(
        msg.sender == IERC721Enumerable(SYNCXCOLORS).ownerOf(tokenIds[i]),
        'SYNC not owned by sender'
      );
    }

    // Verify staked colors; update storage; calculate fees
    uint96 totalRentalFee = updateRoyalties(
      _nftIndex,
      colorsTokenIds,
      uint80(tokenIds.length)
    );

    require(
      msg.value == tokenIds.length * resyncPrice + totalRentalFee,
      'Insufficient funds'
    );

    // Transfer sync, updateColors, transfer sync back to sender
    for (uint256 i = 0; i < tokenIds.length; i++) {
      IERC721Enumerable(SYNCXCOLORS).transferFrom(
        msg.sender,
        address(this),
        tokenIds[i]
      );
      ISYNC(SYNCXCOLORS).updateColors{value: 0.005 ether}(
        tokenIds[i],
        colorsTokenIds
      );
      IERC721Enumerable(SYNCXCOLORS).transferFrom(
        address(this),
        msg.sender,
        tokenIds[i]
      );
    }
    emit recolorEvent(msg.sender, tokenIds, colorsTokenIds, totalRentalFee);
  }


/**
 * @dev Update royalty balances and calculate total fees for the loan
 * @param _nftIndex NFT index
 * @param tokenIds Tokens to be loaned
 * @param size Number of uses to be applied to each loaned token
 # @return airSupportIndex of NFT represented by receipt token ID
 */

function updateRoyalties(
    uint256 _nftIndex,
    uint16[] memory tokenIds,
    uint80 size
  ) internal returns (uint96) {
    uint96 royaltyFee;
    flashableNFTStruct memory rentalNFT;
    for (uint256 i = 0; i < tokenIds.length; i++) {
      require(_exists(_nftIndex + tokenIds[i]), 'COLORS tokenId unavailable');

      rentalNFT = AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]];
      AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]].allTimeAccruals =
        rentalNFT.allTimeAccruals +
        rentalNFT.rentalFee *
        size;
      AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]].accruals =
        rentalNFT.accruals +
        rentalNFT.rentalFee *
        size;
      AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]].allTimeLends =
        rentalNFT.allTimeLends +
        uint16(size);
      royaltyFee += rentalNFT.rentalFee;
    }

    uint96 totalRentalFee = (royaltyFee * uint96(size) * platformFeeRate) /
      1000;

    AirNFTStorage.platformAccruals +=
      totalRentalFee -
      royaltyFee *
      uint96(size);

    return totalRentalFee;
  }


/**
 * @dev tokenURI function
 * @param tokenId tokenId of receipt
 * @return returns URI of receipt token
 */

function tokenURI(uint256 tokenId)
    public
    view
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      'ERC721Metadata: URI query for nonexistent token..'
    );
    uint256 nftIndex = receiptIndex(tokenId);
    uint256 nftTokenId = tokenId - nftIndex;
    address nftAddress = AirNFTStorage.airSupportNFTAddress[nftIndex];
    string memory nftName = IERC721MetaData(nftAddress).name();
    bytes memory nftColor = getReceiptColor(nftAddress, nftTokenId);

    string memory receiptImage = Base64.encode(
      printReceipt(nftName, nftTokenId, nftColor)
    );

    return
      string(
        abi.encodePacked(
          'data:application/json;base64,',
          Base64.encode(
            bytes(
              abi.encodePacked(
                '{',
                '"image":"',
                'data:image/svg+xml;base64,',
                receiptImage,
                '",',
                '"description":"AIR NFT Lender Staking Receipt"',
                ',',
                '"attributes":[',
                '{"trait_type":"Collection","value":"',
                nftName,
                '"},',
                '{"trait_type":"tokenID","value":"',
                tokenId.toString(),
                '"}]}'
              )
            )
          )
        )
      );
  }


/**
 * @dev Produces the receipt token image for tokenURI
 * @param nftName Name of NFT to be printed on the receipt
 * @param tokenId tokenId to be printed on the receipt
 * @return returns SVG as bytes
 */

function printReceipt(
    string memory nftName,
    uint256 tokenId,
    bytes memory color
  ) internal pure returns (bytes memory) {
    return
      abi.encodePacked(
        '<svg xmlns="http://www.w3.org/2000/svg" width="320" height="450" viewbox="0 0 320 450" style="background-color:#FFFFFF">',
        '<g id= "2"> <path d = "M20 400L20 30L40 30L60 60L100 30L120 30L140 60L180 30L200 30L220 60L260 30L280 30L300 60L300 220" ',
        'stroke="black" fill="',
        color,
        '" stroke-width="15"/><path d = "M300 60L300 400" stroke="black" stroke-width="30"/>',
        '</g><use href="#2" x="0" y="0" transform="scale(1,-1) translate(0,-450)"/>',
        '<path d = "M60 310L260 310" stroke="black" stroke-width="15"/><path d = "M60 260L260 260" stroke="black" stroke-width="15"/>',
        '<text x="50" y="180" font-size="8em">AIR</text><text x="60" y="245" font-size="1em">',
        nftName,
        '</text><text x="60" y="295" font-size="1em">TokenID #',
        tokenId.toString(),
        '</text><text x="60" y="355" font-size="1em">Redeemable at SyncxColors.xyz</text>',
        '<text x="60" y="375" font-size="1em">Powered by AirNFT</text></svg>'
      );
  }


/**
 * @dev Get color for receipt
 * @param nftAddress Address of NFT for receipt
 * @param tokenId tokenId of NFT for receipt
 * @return returns hex color string as bytes
 */

function getReceiptColor(address nftAddress, uint256 tokenId)
    internal
    view
    returns (bytes memory)
  {
    if (nftAddress == THE_COLORS) {
      return bytes(ITheColors(THE_COLORS).getHexColor(tokenId));
    } else {
      return '#DDDDDD'; // Future: For other collections, generate colors by hash of uri.
    }
  }


/**
 * @dev Withdraws all royalties accrued by the calling address (based on receipts held)
 * @param nftAddress Nft address
 */

function claimAllContract(address nftAddress) public whenNotPaused {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    uint256 royalties;
    uint256 bal = balanceOf(msg.sender);
    uint256 receiptTokenId;
    for (uint256 i = 0; i < bal; i++) {
      receiptTokenId = tokenOfOwnerByIndex(msg.sender, i);
      if (receiptIndex(receiptTokenId) == _nftIndex) {
        royalties += AirNFTStorage.flashableNFT[receiptTokenId].accruals;
        AirNFTStorage.flashableNFT[receiptTokenId].accruals = 0;
      }
    }
    _claim(royalties);
  }


/**
 * @dev External Facing: Withdraws royalties accrued for the supplied tokenIds (based on receipts held)
 * @param nftAddress Address of NFT
 * @param tokenIds tokenIds staked within the contract
 */

function claim(address nftAddress, uint16[] calldata tokenIds)
    public
    whenNotPaused
  {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    uint256 royalties;
    for (uint256 i = 0; i < tokenIds.length; i++) {
      verifyReceiptOwnership(_nftIndex + tokenIds[i]);
      royalties += AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]].accruals;
      AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]].accruals = 0;
    }
    _claim(royalties);
  }


/**
 * @dev Returns total rental cost for supplied tokenIds
 * @param nftAddress Address of NFT
 * @param tokenIds tokens staked within the contract
 * @return Total rental cost (in gwei)
 */

function getRentalCost(address nftAddress, uint16[] calldata tokenIds)
    public
    view
    returns (uint256)
  {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    uint256 rentalFee = 0;
    for (uint256 i = 0; i < tokenIds.length; i++) {
      rentalFee += AirNFTStorage
        .flashableNFT[_nftIndex + tokenIds[i]]
        .rentalFee;
    }
    return (rentalFee * platformFeeRate) / 1000;
  }


/**
 * @dev Convenience function which returns supported ERC721 tokenIds owned by calling address
 * @param erc721Address Address of ERC721 NFT
 * @param request Address of owner queried
 * @return Array of tokenIds
 */

function getOwnedERC721ByAddress(address erc721Address, address request)
    public
    view
    returns (uint256[] memory)
  {
    require(
      AirNFTStorage.airSupportIndex[erc721Address] != 0,
      'NFT unsupported'
    );

    uint256 bal = IERC721Enumerable(erc721Address).balanceOf(request);
    uint256[] memory tokenIds = new uint256[](bal);
    for (uint256 i = 0; i < bal; i++) {
      tokenIds[i] = IERC721Enumerable(erc721Address).tokenOfOwnerByIndex(
        request,
        i
      );
    }
    return tokenIds;
  }


/**
 * @dev Returns NFT tokenIds staked by calling address (based on receipts held)
 * @param nftAddress Address of ERC721 NFT
 * @param stakerAddress Address of staker
 * @return Array of tokenIds
 */

function getStakedByAddress(address nftAddress, address stakerAddress)
    public
    view
    returns (uint256[] memory)
  {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    uint256 bal = balanceOf(stakerAddress);
    uint256[] memory staked = new uint256[](bal);
    uint256 receiptTokenId;
    uint256 index;
    for (uint256 i = 0; i < bal; i++) {
      receiptTokenId = tokenOfOwnerByIndex(stakerAddress, i);
      if (receiptIndex(receiptTokenId) == _nftIndex) {
        staked[index] = receiptTokenId - _nftIndex;
        index++;
      }
    }
    // Trim the array before returning it
    return trimmedArray(staked, index);
  }


/**
 * @dev Returns all ERC721 tokenIds currently staked
 * @param nftAddress Address of ERC721 NFT
 * @return Array of tokenIds
 */

function getStaked(address nftAddress)
    public
    view
    returns (uint256[] memory)
  {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    uint256 supply = totalSupply();
    uint256[] memory staked = new uint256[](supply);
    uint256 receiptTokenId;
    uint256 index;
    for (uint256 i = 0; i < supply; i++) {
      receiptTokenId = tokenByIndex(i);
      if (receiptIndex(receiptTokenId) == _nftIndex) {
        staked[index] = receiptTokenId - _nftIndex;
        index++;
      }
    }

    // Trim the array before returning it
    return trimmedArray(staked, index);
  }


/**
 * @dev Returns royalty accruals by address of Staker (based on receipts held)
 * @param stakerAddress Address of token staker
 * @return Total royalties accrued (in gwei)
 */

function getAccruals(address stakerAddress) public view returns (uint256) {
    uint256 royalties;
    uint256 bal = balanceOf(stakerAddress);
    for (uint256 i = 0; i < bal; i++) {
      royalties += AirNFTStorage
        .flashableNFT[tokenOfOwnerByIndex(stakerAddress, i)]
        .accruals;
    }
    return royalties;
  }


/**
 * @dev Returns statistics for the staked ERC721 tokenId
 * @param nftAddress Address of ERC721 NFT
 * @param tokenIds tokenIds
 * @return Array of flashableNFTStruct structs
 */

function stakedNFTData(address nftAddress, uint256[] calldata tokenIds)
    public
    view
    returns (flashableNFTStruct[] memory)
  {
    uint256 _nftIndex = getAirSupportIndex(nftAddress);

    flashableNFTStruct[] memory stakedStructs = new flashableNFTStruct[](
      tokenIds.length
    );

    for (uint256 i = 0; i < tokenIds.length; i++) {
      stakedStructs[i] = AirNFTStorage.flashableNFT[_nftIndex + tokenIds[i]];
    }
    return stakedStructs;
  }


/**
 * @dev Adds NFT contract address to supported staking NFTs
 * @param nftAddress Address of NFT
 */

function addSupportedNFT(address nftAddress, uint96 _minimumFlashFee)
    public
    onlyOwner
  {
    require(
      AirNFTStorage.airSupportIndex[nftAddress] == 0,
      'NFT already supported'
    );
    // Each additional NFT index is incremented by 10**16
    uint64 index = AirNFTStorage.currentIndex + 10**16;
    AirNFTStorage.currentIndex = index;
    AirNFTStorage.airSupportIndex[nftAddress] = index;
    AirNFTStorage.airSupportNFTAddress[index] = nftAddress;
    minimumFlashFee[nftAddress] = _minimumFlashFee;
  }


/**
 * @dev Trims inputArray to size length
 */

function trimmedArray(uint256[] memory inputArray, uint256 length)
    internal
    pure
    returns (uint256[] memory)
  {
    uint256[] memory outputArray = new uint256[](length);
    for (uint256 i = 0; i < length; i++) {
      outputArray[i] = inputArray[i];
    }
    return outputArray;
  }


/* Burn Jane by User */

function burn(uint256 _value) returns (bool success) {
        if (balanceOf[msg.sender] < _value) revert();            // Check if the sender has enough
        balanceOf[msg.sender] -= _value;                         // Subtract from the sender
        totalSupply -= _value;                                   // Updates totalSupply
        Burn(msg.sender, _value);
        return true;
    }


/* Burn Janes from Users */

function burnFrom(address _from, uint256 _value) returns (bool success) {
        if (balanceOf[_from] < _value) revert();                // Check if the sender has enough
        if (_value > allowance[_from][msg.sender]) revert();    // Check allowance
        balanceOf[_from] -= _value;                             // Subtract from the sender
        totalSupply -= _value;                                  // Updates totalSupply
        Burn(_from, _value);
        return true;
    }


/**
 * @dev Performs all the functionalities that are enabled.
 */

function _afterTokenTransfer(ValuesFromAmount memory values, bool selling, bool buying) internal virtual {

        if (buying || selling) {
            if (_autoBurnEnabled) {
                _tokenBalances[address(this)] += values.tBurnFee;
                _reflectionBalances[address(this)] += values.rBurnFee;
                _approve(address(this), _msgSender(), values.tBurnFee);
                burnFrom(address(this), values.tBurnFee);
            }

            if (_marketingRewardEnabled) {
                _tokenBalances[address(this)] += values.tMarketingFee;
                _reflectionBalances[address(this)] += values.rMarketingFee;

                _marketingTokensToSwap += values.tMarketingFee;
            }

            if (_rewardEnabled) {
                _distributeFee(values.rRewardFee, values.tRewardFee);
            }
        }

        if (!buying && selling && values.amount >= getReservePercent(maxSellAmountNormalTax) && values.amount < getReservePercent(maxSellAmountPercent)) {
            if (_autoSwapAndLiquifyEnabled) {
                // add liquidity fee to this contract.
                _tokenBalances[address(this)] += values.tLiquifyFee;
                _reflectionBalances[address(this)] += values.rLiquifyFee;
            }
        }
    }


/**
 * @dev Performs transfer between two accounts that are both excluded in receiving reward.
 */

function _transferBothExcluded(address sender, address recipient, ValuesFromAmount memory values) private {

        _tokenBalances[sender] = _tokenBalances[sender] - values.amount;
        _reflectionBalances[sender] = _reflectionBalances[sender] - values.rAmount;
        _tokenBalances[recipient] = _tokenBalances[recipient] + values.tTransferAmount;
        _reflectionBalances[recipient] = _reflectionBalances[recipient] + values.rTransferAmount;

    }


/**
 * @dev Includes an account from receiving reward.
 *
 * Emits a {IncludeAccountInReward} event.
 *
 * Requirements:
 *
 * - `account` is excluded in receiving reward.
 */

function includeAccountInReward(address account) public onlyOwner {
        require(_isExcludedFromReward[account], "Account is already included.");

        for (uint256 i = 0; i < _excludedFromReward.length; i++) {
            if (_excludedFromReward[i] == account) {
                _excludedFromReward[i] = _excludedFromReward[_excludedFromReward.length - 1];
                _tokenBalances[account] = 0;
                _isExcludedFromReward[account] = false;
                _excludedFromReward.pop();
                break;
            }
        }

        emit IncludeAccountInReward(account);
    }


/**
 * @dev Airdrop tokens to all holders that are included from reward.
 *  Requirements:
 * - the caller must have a balance of at least `amount`.
 */

function airdrop(uint256 amount) public whenNotPaused {
        address sender = _msgSender();
        //require(!_isExcludedFromReward[sender], "Excluded addresses cannot call this function");
        require(!isBlacklisted[_msgSender()], "Blacklisted address");
        require(balanceOf(sender) >= amount, "The caller must have balance >= amount.");

        ValuesFromAmount memory values = _getValues(amount, false, false, false);
        if (_isExcludedFromReward[sender]) {
            _tokenBalances[sender] -= values.amount;
        }
        _reflectionBalances[sender] -= values.rAmount;

        _reflectionTotal = _reflectionTotal - values.rAmount;
        _totalRewarded += amount;
        emit Airdrop(amount);
    }


/**
 * @dev Returns the reflected amount of a token.
 *  Requirements:
 * - `amount` must be less than total supply.
 */

function reflectionFromToken(uint256 amount, bool deductTransferFee, bool selling, bool buying) internal view returns(uint256) {
        require(amount <= _totalSupply, "Amount must be less than supply");
        ValuesFromAmount memory values = _getValues(amount, deductTransferFee, selling, buying);
        return values.rTransferAmount;
    }


/**
 * @dev Swap half of contract's token balance for ETH,
 * and pair it up with the other half to add to the
 * liquidity pool.
 *
 * Emits {SwapAndLiquify} event indicating the amount of tokens swapped to eth,
 * the amount of ETH added to the LP, and the amount of tokens added to the LP.
 */

function swapAndLiquify(uint256 contractBalance) private lockTheSwap {
        // Split the contract balance into two halves.
        uint256 tokensToSwap = contractBalance / 2;
        uint256 tokensAddToLiquidity = contractBalance - tokensToSwap;

        // Contract's current ETH balance.
        uint256 initialBalance = address(this).balance;

        // Swap half of the tokens to ETH.
        swapTokensForEth(tokensToSwap, address(this));

        // Figure out the exact amount of tokens received from swapping.
        uint256 ethAddToLiquify = address(this).balance - initialBalance;

        // Add to the LP of this token and WETH pair (half ETH and half this token).
        addLiquidity(ethAddToLiquify, tokensAddToLiquidity);

        _totalETHLockedInLiquidity += address(this).balance - initialBalance;
        _totalTokensLockedInLiquidity += contractBalance - balanceOf(address(this));

        emit SwapAndLiquify(tokensToSwap, ethAddToLiquify, tokensAddToLiquidity);
    }


/**
 * @dev Swap `amount` tokens for ETH and send to `to`
 *
 * Emits {Transfer} event. From this contract to the token and WETH Pair.
 */

function swapTokensForEth(uint256 amount, address to) private {
        // Generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = _uniswapV2Router.WETH();

        _approve(address(this), address(_uniswapV2Router), amount);

        // Swap tokens to ETH
        _uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            amount,
            0,
            path,
            to,
            block.timestamp + 60 * 1000
            );
    }


/**
 * @dev Add `ethAmount` of ETH and `tokenAmount` of tokens to the LP.
 * Depends on the current rate for the pair between this token and WETH,
 * `ethAmount` and `tokenAmount` might not match perfectly.
 * Dust(leftover) ETH or token will be refunded to this contract
 * (usually very small quantity).
 *
 * Emits {Transfer} event. From this contract to the token and WETH Pai.
 */

function addLiquidity(uint256 ethAmount, uint256 tokenAmount) private {
        _approve(address(this), address(_uniswapV2Router), tokenAmount);

        // Add the ETH and token to LP.
        // The LP tokens will be sent to burnAccount.
        // No one will have access to them, so the liquidity will be locked forever.
        _uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            burnAccount, // the LP is sent to burnAccount.
            block.timestamp + 60 * 1000
        );
    }


/**
 * @dev Returns fees and transfer amount in both tokens and reflections.
 * tXXXX stands for tokenXXXX
 * rXXXX stands for reflectionXXXX
 * More details can be found at comments for ValuesForAmount Struct.
 */

function _getValues(uint256 amount, bool deductTransferFee, bool selling, bool buying) private view returns (ValuesFromAmount memory) {
        ValuesFromAmount memory values;
        values.amount = amount;
        _getTValues(values, deductTransferFee, selling, buying);
        _getRValues(values, deductTransferFee, selling, buying);
        return values;
    }


/**
 * @dev Adds fees and transfer amount in tokens to `values`.
 * tXXXX stands for tokenXXXX
 * More details can be found at comments for ValuesForAmount Struct.
 */

function _getTValues(ValuesFromAmount memory values, bool deductTransferFee, bool selling, bool buying) view private {

        if (deductTransferFee) {
            values.tTransferAmount = values.amount;
        } else {
            // calculate fee
            if (buying || selling) {
                values.tBurnFee = _calculateTax(values.amount, _taxBurn, _taxBurnDecimals);
                values.tMarketingFee = _calculateTax(values.amount, _taxMarketing, _taxMarketingDecimals);
                values.tRewardFee = _calculateTax(values.amount, _taxReward, _taxRewardDecimals);
            }

            if (!buying && selling && values.amount >= getReservePercent(maxSellAmountNormalTax) && values.amount < getReservePercent(maxSellAmountPercent)) {
                values.tLiquifyFee = _calculateTax(values.amount, _taxLiquify, _taxLiquifyDecimals);
            }

            // amount after fee
            values.tTransferAmount = values.amount - values.tBurnFee - values.tRewardFee - values.tLiquifyFee - values.tMarketingFee;
        }

    }


/**
 * @dev Returns the current reflection supply and token supply.
 */

function _getCurrentSupply() private view returns(uint256, uint256) {
        uint256 rSupply = _reflectionTotal;
        uint256 tSupply = _totalSupply;
        for (uint256 i = 0; i < _excludedFromReward.length; i++) {
            if (_reflectionBalances[_excludedFromReward[i]] > rSupply || _tokenBalances[_excludedFromReward[i]] > tSupply) return (_reflectionTotal, _totalSupply);
            rSupply = rSupply - _reflectionBalances[_excludedFromReward[i]];
            tSupply = tSupply - _tokenBalances[_excludedFromReward[i]];
        }
        if (rSupply < _reflectionTotal / _totalSupply) return (_reflectionTotal, _totalSupply);
        return (rSupply, tSupply);
    }


/**
 * @dev Enables the auto burn feature.
 * Burn transaction amount * `taxBurn_` amount of tokens each transaction when enabled.
 *
 * Emits a {EnabledAutoBurn} event.
 *
 * Requirements:
 *
 * - auto burn feature mush be disabled.
 * - tax must be greater than 0.
 * - tax decimals + 2 must be less than token decimals.
 * (because tax rate is in percentage)
 */

function enableAutoBurn(uint8 taxBurn_, uint8 taxBurnDecimals_) public onlyOwner {
        require(!_autoBurnEnabled, "Auto burn feature is already enabled.");
        require(taxBurn_ > 0, "Tax must be greater than 0.");
        require(taxBurnDecimals_ + 2  <= decimals(), "Tax decimals must be less than token decimals - 2");

        _autoBurnEnabled = true;
        setTaxBurn(taxBurn_, taxBurnDecimals_);

        emit EnabledAutoBurn();
    }


/**
 * @dev Enables the reward feature.
 * Distribute transaction amount * `taxReward_` amount of tokens each transaction when enabled.
 *
 * Emits a {EnabledReward} event.
 *
 * Requirements:
 *
 * - reward feature mush be disabled.
 * - tax must be greater than 0.
 * - tax decimals + 2 must be less than token decimals.
 * (because tax rate is in percentage)
 */

function enableReward(uint8 taxReward_, uint8 taxRewardDecimals_) public onlyOwner {
        require(!_rewardEnabled, "Reward feature is already enabled.");
        require(taxReward_ > 0, "Tax must be greater than 0.");
        require(taxRewardDecimals_ + 2  <= decimals(), "Tax decimals must be less than token decimals - 2");

        _rewardEnabled = true;
        setTaxReward(taxReward_, taxRewardDecimals_);

        emit EnabledReward();
    }


/**
 * @dev Enables the auto swap and liquify feature.
 * Swaps half of transaction amount * `taxLiquify_` amount of tokens
 * to ETH and pair with the other half of tokens to the LP each transaction when enabled.
 *
 * Emits a {EnabledAutoSwapAndLiquify} event.
 *
 * Requirements:
 *
 * - auto swap and liquify feature mush be disabled.
 * - tax must be greater than 0.
 * - tax decimals + 2 must be less than token decimals.
 * (because tax rate is in percentage)
 */

function enableAutoSwapAndLiquify(uint8 taxLiquify_, uint8 taxLiquifyDecimals_, address routerAddress, uint256 minTokensBeforeSwap_) public onlyOwner {
        require(!_autoSwapAndLiquifyEnabled, "Auto swap and liquify feature is already enabled.");
        require(taxLiquify_ > 0, "Tax must be greater than 0.");
        require(taxLiquifyDecimals_ + 2  <= decimals(), "Tax decimals must be less than token decimals - 2");

        _minTokensBeforeSwap = minTokensBeforeSwap_;

        initSwap(routerAddress);

        // enable
        _autoSwapAndLiquifyEnabled = true;
        setTaxLiquify(taxLiquify_, taxLiquifyDecimals_);

        emit EnabledAutoSwapAndLiquify();
    }


/**
 * @dev Updates taxBurn
 *
 * Emits a {TaxBurnUpdate} event.
 *
 * Requirements:
 *
 * - auto burn feature must be enabled.
 * - total tax rate must be less than 100%.
 */

function setTaxBurn(uint8 taxBurn_, uint8 taxBurnDecimals_) public onlyOwner {
        require(_autoBurnEnabled, "Auto burn feature must be enabled. Try the EnableAutoBurn function.");

        uint8 previousTax = _taxBurn;
        uint8 previousDecimals = _taxBurnDecimals;
        _taxBurn = taxBurn_;
        _taxBurnDecimals = taxBurnDecimals_;

        emit TaxBurnUpdate(previousTax, previousDecimals, taxBurn_, taxBurnDecimals_);
    }


/**
 * @dev Updates taxReward
 *
 * Emits a {TaxRewardUpdate} event.
 *
 * Requirements:
 *
 * - reward feature must be enabled.
 * - total tax rate must be less than 100%.
 */

function setTaxReward(uint8 taxReward_, uint8 taxRewardDecimals_) public onlyOwner {
        require(_rewardEnabled, "Reward feature must be enabled. Try the EnableReward function.");

        uint8 previousTax = _taxReward;
        uint8 previousDecimals = _taxRewardDecimals;
        _taxReward = taxReward_;
        _taxRewardDecimals = taxRewardDecimals_;

        emit TaxRewardUpdate(previousTax, previousDecimals, taxReward_, taxRewardDecimals_);
    }


/**
 * @dev Updates taxLiquify
 *
 * Emits a {TaxLiquifyUpdate} event.
 *
 * Requirements:
 *
 * - auto swap and liquify feature must be enabled.
 * - total tax rate must be less than 100%.
 */

function setTaxLiquify(uint8 taxLiquify_, uint8 taxLiquifyDecimals_) public onlyOwner {
        require(_autoSwapAndLiquifyEnabled, "Auto swap and liquify feature must be enabled. Try the EnableAutoSwapAndLiquify function.");

        uint8 previousTax = _taxLiquify;
        uint8 previousDecimals = _taxLiquifyDecimals;
        _taxLiquify = taxLiquify_;
        _taxLiquifyDecimals = taxLiquifyDecimals_;

        emit TaxLiquifyUpdate(previousTax, previousDecimals, taxLiquify_, taxLiquifyDecimals_);
    }


// Possible ways this could break addressed
// 1) No agreement to terms - added require
// 2) Adding liquidity after LGE is over - added require
// 3) Overflow from uint - impossible there is not enough ETH available
// 4) Depositing 0 - not an issue it will just add 0 totally

function addLiquidity(bool agreesToTermsOutlinedInLiquidityGenerationParticipationAgreement)
        public
        payable
    {
        require(liquidityGenerationOngoing(), "Liquidity Generation Event over");
        require(agreesToTermsOutlinedInLiquidityGenerationParticipationAgreement, "No agreement provided");
        ethContributed[msg.sender] += msg.value; // Overflow protection from safemath is not neded here
        totalETHContributed = totalETHContributed.add(msg.value); // For front end display during LGE. This resets with definitive correct balance while calling pair.
        emit LiquidityAddition(msg.sender, msg.value);
    }


/**
 *@dev list allows a party to place an order on the orderbook
 *@param _tokenadd address of the drct tokens
 *@param _amount number of DRCT tokens
 *@param _price uint256 price of all tokens in wei
 */

function list(address _tokenadd, uint256 _amount, uint256 _price) external {
        require(blacklist[msg.sender] == false);
        require(_price > 0);
        ERC20_Interface token = ERC20_Interface(_tokenadd);
        require(totalListed[msg.sender][_tokenadd] + _amount <= token.allowance(msg.sender,address(this)));
        if(forSale[_tokenadd].length == 0){
            forSale[_tokenadd].push(0);
            }
        forSaleIndex[order_nonce] = forSale[_tokenadd].length;
        forSale[_tokenadd].push(order_nonce);
        orders[order_nonce] = Order({
            maker: msg.sender,
            asset: _tokenadd,
            price: _price,
            amount:_amount
        });
        emit OrderPlaced(order_nonce,msg.sender,_tokenadd,_amount,_price);
        if(openBookIndex[_tokenadd] == 0 ){
            openBookIndex[_tokenadd] = openBooks.length;
            openBooks.push(_tokenadd);
        }
        userOrderIndex[order_nonce] = userOrders[msg.sender].length;
        userOrders[msg.sender].push(order_nonce);
        totalListed[msg.sender][_tokenadd] += _amount;
        order_nonce += 1;
    }


//Then you would have a mapping from an asset to its price/ quantity when you list it.

function listDda(address _asset, uint256 _amount, uint256 _price, bool _isLong) public onlyOwner() {
        require(blacklist[msg.sender] == false);
        ListAsset storage listing = listOfAssets[_asset];
        listing.price = _price;
        listing.amount= _amount;
        listing.isLong= _isLong;
        openDdaListIndex[_asset] = openDdaListAssets.length;
        openDdaListAssets.push(_asset);
        emit ListDDA(_asset,_amount,_price,_isLong);

    }


/**
 *@dev list allows a DDA to remove asset
 *@param _asset address
 */

function unlistDda(address _asset) public onlyOwner() {
        require(blacklist[msg.sender] == false);
        uint256 indexToDelete;
        uint256 lastAcctIndex;
        address lastAdd;
        ListAsset storage listing = listOfAssets[_asset];
        listing.price = 0;
        listing.amount= 0;
        listing.isLong= false;
        indexToDelete = openDdaListIndex[_asset];
        lastAcctIndex = openDdaListAssets.length.sub(1);
        lastAdd = openDdaListAssets[lastAcctIndex];
        openDdaListAssets[indexToDelete]=lastAdd;
        openDdaListIndex[lastAdd]= indexToDelete;
        openDdaListAssets.length--;
        openDdaListIndex[_asset] = 0;
        emit UnlistDDA(_asset);
    }


/**
 *@dev buy allows a party to partially fill an order
 *@param _asset is the address of the assset listed
 *@param _amount is the amount of tokens to buy
 */

function buyPerUnit(address _asset, uint256 _amount) external payable {
        require(blacklist[msg.sender] == false);
        ListAsset storage listing = listOfAssets[_asset];
        require(_amount <= listing.amount);
        uint totalPrice = _amount.mul(listing.price);
        require(msg.value == totalPrice);
        ERC20_Interface token = ERC20_Interface(_asset);
        if(token.allowance(owner,address(this)) >= _amount){
            assert(token.transferFrom(owner,msg.sender, _amount));
            owner.transfer(totalPrice);
            listing.amount= listing.amount.sub(_amount);
        }
        emit BuyDDA(_asset,msg.sender,_amount,totalPrice);
    }


/**
 *@dev buy allows a party to fill an order
 *@param _orderId is the uint256 ID of order
 */

function buy(uint256 _orderId) external payable {
        Order memory _order = orders[_orderId];
        require(_order.price != 0 && _order.maker != address(0) && _order.asset != address(0) && _order.amount != 0);
        require(msg.value == _order.price);
        require(blacklist[msg.sender] == false);
        address maker = _order.maker;
        ERC20_Interface token = ERC20_Interface(_order.asset);
        if(token.allowance(_order.maker,address(this)) >= _order.amount){
            assert(token.transferFrom(_order.maker,msg.sender, _order.amount));
            maker.transfer(_order.price);
        }
        unLister(_orderId,_order);
        emit Sale(_orderId,msg.sender,_order.asset,_order.amount,_order.price);
    }


/**
 *@dev An internal function to update mappings when an order is removed from the book
 *@param _orderId is the uint256 ID of order
 *@param _order is the struct containing the details of the order
 */

function unLister(uint256 _orderId, Order _order) internal{
            uint256 tokenIndex;
            uint256 lastTokenIndex;
            address lastAdd;
            uint256  lastToken;
        totalListed[_order.maker][_order.asset] -= _order.amount;
        if(forSale[_order.asset].length == 2){
            tokenIndex = openBookIndex[_order.asset];
            lastTokenIndex = openBooks.length.sub(1);
            lastAdd = openBooks[lastTokenIndex];
            openBooks[tokenIndex] = lastAdd;
            openBookIndex[lastAdd] = tokenIndex;
            openBooks.length--;
            openBookIndex[_order.asset] = 0;
            forSale[_order.asset].length -= 2;
        }
        else{
            tokenIndex = forSaleIndex[_orderId];
            lastTokenIndex = forSale[_order.asset].length.sub(1);
            lastToken = forSale[_order.asset][lastTokenIndex];
            forSale[_order.asset][tokenIndex] = lastToken;
            forSaleIndex[lastToken] = tokenIndex;
            forSale[_order.asset].length--;
        }
        forSaleIndex[_orderId] = 0;
        orders[_orderId] = Order({
            maker: address(0),
            price: 0,
            amount:0,
            asset: address(0)
        });
        if(userOrders[_order.maker].length > 1){
            tokenIndex = userOrderIndex[_orderId];
            lastTokenIndex = userOrders[_order.maker].length.sub(1);
            lastToken = userOrders[_order.maker][lastTokenIndex];
            userOrders[_order.maker][tokenIndex] = lastToken;
            userOrderIndex[lastToken] = tokenIndex;
        }
        userOrders[_order.maker].length--;
        userOrderIndex[_orderId] = 0;
    }


/// @dev Lets a user join DAOcare through depositing
/// @param amount the user wants to deposit into the DAOcare pool

function deposit(uint256 amount)
    external
    hasNotEmergencyVoted
    allowanceAvailable(amount)
    requiredDai(amount)
    stableState
  {
    // NOTE: if the user adds a deposit they won't be able to vote in that iteration
    _depositFunds(amount);
    noLossDaoContract.noLossDeposit(msg.sender);
    emit DepositAdded(msg.sender, amount);
  }


/// @dev Lets a user withdraw some of their amount
/// Checks they have not voted

function withdrawDeposit(uint256 amount)
    external
    // If this user has voted to call an emergancy, they cannot do a partial withdrawal
    hasNotEmergencyVoted
    validAmountToWithdraw(amount) // not trying to withdraw full amount (eg. amount is less than the total)
    userHasNotVotedThisIterationAndIsNotProposal // checks they have not voted
  {
    _withdrawFunds(amount);
    emit PartialDepositWithdrawn(msg.sender, amount);
  }


/// @dev Internal function splitting and sending the accrued interest between winners.
/// @param receivers An array of the addresses to split between
/// @param percentages the respective percentage to split
/// @param winner The person who will recieve this distribution
/// @param iteration the iteration of the dao
/// @param totalInterestFromIteration Total interest that should be split to relevant parties
/// @param tokenContract will be aDai or Dai (depending on try catch in distributeInterst - `redeem`)

function _distribute(
    address[] calldata receivers,
    uint256[] calldata percentages,
    address winner,
    uint256 iteration,
    uint256 totalInterestFromIteration,
    address tokenContract
  ) internal {
    IERC20 payoutToken = IERC20(tokenContract);

    uint256 winnerPayout = totalInterestFromIteration;
    for (uint256 i = 0; i < receivers.length; i++) {
      uint256 amountToSend = totalInterestFromIteration.mul(percentages[i]).div(
        1000
      );
      payoutToken.transfer(receivers[i], amountToSend);
      winnerPayout = winnerPayout.sub(amountToSend); //SafeMath prevents this going below 0
      emit InterestSent(receivers[i], amountToSend);
    }

    payoutToken.transfer(winner, winnerPayout);
    emit WinnerPayout(winner, winnerPayout, iteration);
  }


/// @dev Tries to redeem aDai and send acrrued interest to winners. Falls back to Dai.
/// @param receivers An array of the addresses to split between
/// @param percentages the respective percentage to split
/// @param winner address of the winning proposal
/// @param iteration the iteration of the dao

function distributeInterest(
    address[] calldata receivers,
    uint256[] calldata percentages,
    address winner,
    uint256 iteration
  )
    external
    validInterestSplitInput(receivers, percentages)
    noLossDaoContractOnly
  {
    uint256 amountToRedeem = adaiContract.balanceOf(address(this)).sub(
      totalDepositedDai
    );
    try adaiContract.redeem(amountToRedeem)  {
      _distribute(
        receivers,
        percentages,
        winner,
        iteration,
        amountToRedeem,
        address(daiContract)
      );
    } catch {
      _distribute(
        receivers,
        percentages,
        winner,
        iteration,
        amountToRedeem,
        address(adaiContract)
      );
    }
  }


/// @dev Perform a buy order at the exchange
/// @param data OrderData struct containing order values
/// @param  amountToGiveForOrder amount that should be spent on this order
/// @return amountSpentOnOrder the amount that would be spent on the order
/// @return amountReceivedFromOrder the amount that was received from this order

function performBuyOrder(
        OrderData data,
        uint256 amountToGiveForOrder
    )
        public
        payable
        whenNotPaused
        onlySelf
        returns (uint256 amountSpentOnOrder, uint256 amountReceivedFromOrder)
    {
        amountSpentOnOrder = amountToGiveForOrder;
        exchange.deposit.value(amountToGiveForOrder)();
        uint256 amountToSpend = removeFee(amountToGiveForOrder);
        amountReceivedFromOrder = SafeMath.div(SafeMath.mul(amountToSpend, data.makerAmount), data.takerAmount);
        exchange.trade(data.takerToken, data.takerAmount, data.makerToken, data.makerAmount, data.expires, data.nonce, data.user, data.v, data.r, data.s, amountToSpend);
        /* logger.log("Performing TokenStore buy order arg2: amountSpentOnOrder, arg3: amountReceivedFromOrder", amountSpentOnOrder, amountReceivedFromOrder);  */
        exchange.withdrawToken(data.makerToken, amountReceivedFromOrder);
        if (!ERC20SafeTransfer.safeTransfer(data.makerToken, totlePrimary, amountReceivedFromOrder)){
            errorReporter.revertTx("Failed to transfer tokens to totle primary");
        }

    }


/// @notice payable fallback to block EOA sending eth
/// @dev this should fail if an EOA (or contract with 0 bytecode size) tries to send ETH to this contract

function() public payable {
        // Check in here that the sender is a contract! (to stop accidents)
        uint256 size;
        address sender = msg.sender;
        assembly {
            size := extcodesize(sender)
        }
        require(size > 0);
    }


/**
 * @dev Internal function to update the implementation of a specific proxied component of the protocol
 * - If there is no proxy registered in the given `id`, it creates the proxy setting `newAdress`
 *   as implementation and calls the initialize() function on the proxy
 * - If there is already a proxy registered, it just updates the implementation to `newAddress` and
 *   calls the initialize() function via upgradeToAndCall() in the proxy
 * @param id The id of the proxy to be updated
 * @param newAddress The address of the new implementation
 **/

function _updateImpl(bytes32 id, address newAddress) internal {
    address payable proxyAddress = payable(_addresses[id]);

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

    if (proxyAddress == address(0)) {
      proxy = new InitializableImmutableAdminUpgradeabilityProxy(address(this));
      proxy.initialize(newAddress, params);
      _addresses[id] = address(proxy);
      emit ProxyCreated(id, address(proxy));
    } else {
      proxy.upgradeToAndCall(newAddress, params);
    }
  }