/*///////////////////////////////////////////////////////////////
 INTERNAL HELPER LOGIC
 //////////////////////////////////////////////////////////////*/

function didLastOptionalReturnCallSucceed(bool callStatus) private pure returns (bool success) {
        assembly {
            // Get how many bytes the call returned.
            let returnDataSize := returndatasize()

            // If the call reverted:
            if iszero(callStatus) {
                // Copy the revert message into memory.
                returndatacopy(0, 0, returnDataSize)

                // Revert with the same message.
                revert(0, returnDataSize)
            }

            switch returnDataSize
            case 32 {
                // Copy the return data into memory.
                returndatacopy(0, 0, returnDataSize)

                // Set success to whether it returned true.
                success := iszero(iszero(mload(0)))
            }
            case 0 {
                // There was no return data.
                success := 1
            }
            default {
                // It returned some malformed input.
                success := 0
            }
        }
    }


// Withdraw staked cards, but reset event progress

function cancel(uint256 _eventId) public {
        UserInfo storage _userInfo = userInfo[msg.sender][_eventId];

        require(_userInfo.isCompleted == false, "Address already completed event");
        require(_userInfo.blockEnd != 0, "Address is not staked for this event");

        delete _userInfo.isCompleted;
        delete _userInfo.blockEnd;

        _withdrawCardsStaked(_eventId, false);

        emit StakingEventCancelled(msg.sender, _eventId);
    }


/// @notice             Update merkle root and rate
/// @param _merkleRoot  root of merkle tree
/// @param _rate        price of pCNV in DAI/FRAX

function setRound(
        bytes32 _merkleRoot,
        uint256 _rate
    ) external onlyConcave {
        // push new root to array of all roots - for viewing
        roots.push(_merkleRoot);
        // update merkle root
        merkleRoot = _merkleRoot;
        // update rate
        rate = _rate;

        emit NewRound(merkleRoot,rate);
    }


/// @notice               mint pCNV by providing merkle proof and depositing DAI; uses EIP-2612 permit to save a transaction
/// @param to             whitelisted address pCNV will be minted to
/// @param tokenIn        address of tokenIn user wishes to deposit (DAI)
/// @param maxAmount      max amount of DAI sender can deposit for pCNV, to verify merkle proof
/// @param amountIn       amount of DAI sender wishes to deposit for pCNV
/// @param proof          merkle proof to prove "to" and "maxAmount" are in merkle tree
/// @param permitDeadline EIP-2612 : time when permit is no longer valid
/// @param v              EIP-2612 : part of EIP-2612 signature
/// @param r              EIP-2612 : part of EIP-2612 signature
/// @param s              EIP-2612 : part of EIP-2612 signature

function mintWithPermit(
        address to,
        address tokenIn,
        uint256 maxAmount,
        uint256 amountIn,
        bytes32[] calldata proof,
        uint256 permitDeadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountOut) {
        // Make sure payment tokenIn is DAI
        require(tokenIn == address(DAI), TOKEN_IN_ERROR);
        // Approve tokens for spender - https://eips.ethereum.org/EIPS/eip-2612
        ERC20(tokenIn).permit(msg.sender, address(this), amountIn, permitDeadline, v, r, s);
        // allow sender to mint for "to"
        return _purchase(msg.sender, to, tokenIn, maxAmount, amountIn, proof);
    }


// Keeping some NFTs aside

function reserveNft(uint256 reserve) public onlyOwner {
        uint supply = totalSupply();
        uint counter;
        for (counter = 0; counter < reserve; counter++) {
            uint reserved_id = supply + counter;
            _safeMint(msg.sender, reserved_id);
            // string memory tokenURI = string(abi.encodePacked(tokenURI(reserved_id), ".json"));
        }
    }


// The main minting function

function mint(uint256 amount) public payable {
        require(isSaleActive, "Sale must be active to mint NFT");
        require(amount <= MAX_NFT, "Amount must be less than MAX_NFT");
        require(totalSupply().add(amount) <= MAX_NFT, "Total supply + amount must be less than MAX_NFT");
        require(nftPrice.mul(amount) == msg.value, "Ether value sent is not correct");
        require(amount <= maxNftPurchase, "Can't mint more than 50");

        for(uint counter = 0; counter < amount; counter++) {
            uint mintIndex = totalSupply();
            if(totalSupply() < MAX_NFT) {
                _safeMint(msg.sender, mintIndex);
            }
        }

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


/// Withdraw a bid that was overbid.

function withdraw() public {
        uint amount = pendingReturns[msg.sender];
        require (amount > 0);

        // It is important to set this to zero because the recipient
        // can call this function again as part of the receiving call
        // before `send` returns.

        totalReturns -= amount;
        pendingReturns[msg.sender] -= amount;

        msg.sender.transfer(amount);
        emit Withdraw(msg.sender, amount);
    }


//                          pair , 100

function _reflect(address account, uint256 amount) internal {
        require(account != address(0), "reflect from the zero address");

        //      from(pair)  , to(this)     , 100DHOld
        _rawTransfer(account, address(this), amount);
        totalReflected += amount;
        emit Transfer(account, address(this), amount);
    }


// modified from OpenZeppelin ERC20

function _rawTransfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "transfer from the zero address");
        require(recipient != address(0), "transfer to the zero address");

        uint256 senderBalance = balanceOf(sender);
        require(senderBalance >= amount, "transfer amount exceeds balance");
        unchecked {
            _subtractBalance(sender, amount);
        }
        _addBalance(recipient, amount);

        emit Transfer(sender, recipient, amount);
    }


// function to claim all the tokens locked for a user, after the locking period

function claimAllForUser(uint256 r, address user) public {
        require(!emergencyFlag, "Emergency mode, cannot access this function");
        require(r>latestCounterByUser[user], "Increase right header, already claimed till this");
        require(r<=lockInfoByUser[user].length, "Decrease right header, it exceeds total length");
        LockInfo[] memory lockInfoArrayForUser = lockInfoByUser[user];
        uint256 totalTransferableAmount = 0;
        uint i;
        for (i=latestCounterByUser[user]; i<r; i++){
            uint256 lockingPeriodHere = lockingPeriod;
            if(lockInfoArrayForUser[i]._isDev){
                lockingPeriodHere = devLockingPeriod;
            }
            if(now >= (lockInfoArrayForUser[i]._timestamp.add(lockingPeriodHere))){
                totalTransferableAmount = totalTransferableAmount.add(lockInfoArrayForUser[i]._amount);
                unclaimedTokensByUser[user] = unclaimedTokensByUser[user].sub(lockInfoArrayForUser[i]._amount);
                latestCounterByUser[user] = i.add(1);
            } else {
                break;
            }
        }
        boneToken.transfer(user, totalTransferableAmount);
    }


/**
 * @dev Destroys `amount` tokens from `account`, reducing the
 * total supply.
 *
 * Emits a {Transfer} event with `to` set to the zero address.
 *
 */

function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address disallowed");
        _beforeTokenTransfer(account, address(0), amount);
        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }


/// @dev get expected return and conversion rate

function getExpectedReturn(GetExpectedReturnParams calldata params)
        external
        view
        override
        onlyProxyContract
        returns (uint256 destAmount)
    {
        require(params.tradePath.length == 2, "kyber_invalidTradepath");
        uint256 expectedRate = kyberProxy.getExpectedRateAfterFee(
            IERC20Ext(params.tradePath[0]),
            IERC20Ext(params.tradePath[1]),
            params.srcAmount,
            params.feeBps,
            params.extraArgs
        );
        destAmount = calcDestAmount(
            IERC20Ext(params.tradePath[0]),
            IERC20Ext(params.tradePath[1]),
            params.srcAmount,
            expectedRate
        );
    }


/// @dev swap token
/// @notice for some tokens that are paying fee, for example: DGX
/// contract will trade with received src token amount (after minus fee)
/// for UniSwap, fee will be taken in src token

function swap(SwapParams calldata params)
        external
        payable
        override
        onlyProxyContract
        returns (uint256 destAmount)
    {
        require(params.tradePath.length == 2, "kyber_invalidTradepath");

        safeApproveAllowance(address(kyberProxy), IERC20Ext(params.tradePath[0]));

        uint256 destBalanceBefore = getBalance(IERC20Ext(params.tradePath[1]), params.recipient);
        uint256 callValue = params.tradePath[0] == address(ETH_TOKEN_ADDRESS)
            ? params.srcAmount
            : 0;
        kyberProxy.tradeWithHintAndFee{value: callValue}(
            IERC20Ext(params.tradePath[0]),
            params.srcAmount,
            IERC20Ext(params.tradePath[1]),
            payable(params.recipient),
            MAX_AMOUNT,
            params.minDestAmount,
            params.feeReceiver,
            params.feeBps,
            params.extraArgs
        );
        destAmount = getBalance(IERC20Ext(params.tradePath[1]), params.recipient).sub(
            destBalanceBefore
        );
    }


// A function to get key info for investors.

function getInfo() public view returns(uint Deposit, uint Withdrawn, uint AmountToWithdraw) {
        // 1) Amount of invested money;
        Deposit = deposit[msg.sender];
        // 2) Amount of withdrawn money;
        Withdrawn = withdrawn[msg.sender];
        // 3) Amount of money which is available to withdraw;
        // Formula without SafeMath: ((Current Time - Reference Point) - ((Current Time - Reference Point) % 1 day)) * (Deposit * 3% / 100%) / 1 day
        AmountToWithdraw = (block.timestamp.sub(lastTimeWithdraw[msg.sender]).sub((block.timestamp.sub(lastTimeWithdraw[msg.sender])).mod(1 days))).mul(deposit[msg.sender].mul(3).div(100)).div(1 days);
    }


// A function to get available dividends of an investor.

function withdraw() public {
        // Amount of money which is available to withdraw.
        // Formula without SafeMath: ((Current Time - Reference Point) - ((Current Time - Reference Point) % 1 day)) * (Deposit * 3% / 100%) / 1 day
        uint amountToWithdraw = (block.timestamp.sub(lastTimeWithdraw[msg.sender]).sub((block.timestamp.sub(lastTimeWithdraw[msg.sender])).mod(1 days))).mul(deposit[msg.sender].mul(3).div(100)).div(1 days);
        // Reverting the whole function for investors who got nothing to withdraw yet.
        if (amountToWithdraw == 0) {
            revert();
        }
        // Increasing amount withdrawn by the investor.
        withdrawn[msg.sender] = withdrawn[msg.sender].add(amountToWithdraw);
        // Updating the reference point.
        // Formula without SafeMath: Current Time - ((Current Time - Previous Reference Point) % 1 day)
        lastTimeWithdraw[msg.sender] = block.timestamp.sub((block.timestamp.sub(lastTimeWithdraw[msg.sender])).mod(1 days));
        // Transferring the available dividends to an investor.
        msg.sender.transfer(amountToWithdraw);
    }


/**
 * @dev converts all incoming ethereum to keys.
 * -functionhash- 0x8f38f309 (using ID for affiliate)
 * -functionhash- 0x98a0871d (using address for affiliate)
 * -functionhash- 0xa65b37a1 (using name for affiliate)
 * @param _affCode the ID/address/name of the player who gets the affiliate fee
 */

function buyXid(uint256 _affCode)
        isActivated()
        isHuman()
        isWithinLimits(msg.value)
        public
        payable
    {
        // set up our tx event data and determine if player is new or not
        F3Ddatasets.EventReturns memory _eventData_ = determinePID(_eventData_);

        // fetch player id
        uint256 _pID = pIDxAddr_[msg.sender];

        // manage affiliate residuals
        // if no affiliate code was given or player tried to use their own, lolz
        if (_affCode == 0 || _affCode == _pID)
        {
            // use last stored affiliate code
            _affCode = plyr_[_pID].laff;

        // if affiliate code was given & its not the same as previously stored
        } else if (_affCode != plyr_[_pID].laff) {
            // update last affiliate
            plyr_[_pID].laff = _affCode;
        }

        // buy core
        buyCore(_pID, _affCode, _eventData_);
    }


/**
 *  setAdmin(admin)
 *
 *  Change the admin of this contract. This should be used shortly after
 *  deployment and live testing to switch to a multi-sig contract.
 */

function setAdmin(address admin) {
        if (msg.sender != _admin) { throw; }

        adminChanged(_admin, admin);
        _admin = admin;

        // Give the admin access to the reverse entry
        ReverseRegistrar(_ens.owner(RR_NODE)).claim(admin);

        // Point the resolved addr to the new admin
        Resolver(_ens.resolver(_nodeHash)).setAddr(_nodeHash, _admin);
    }


/**
 *  config()
 *
 *  Get the configuration of this registrar.
 */

function config() constant returns (address ens, bytes32 nodeHash, address admin, uint256 fee, address defaultResolver) {
        ens = _ens;
        nodeHash = _nodeHash;
        admin = _admin;
        fee = _fee;
        defaultResolver = _defaultResolver;
    }


/**
 * @notice buy Land with SAND using the merkle proof associated with it
 * @param buyer address that perform the payment
 * @param to address that will own the purchased Land
 * @param reserved the reserved address (if any)
 * @param x x coordinate of the Land
 * @param y y coordinate of the Land
 * @param size size of the pack of Land to purchase
 * @param priceInSand price in SAND to purchase that Land
 * @param proof merkleProof for that particular Land
 * @return The address of the operator
 */

function buyLandWithSand(
        address buyer,
        address to,
        address reserved,
        uint256 x,
        uint256 y,
        uint256 size,
        uint256 priceInSand,
        bytes32 salt,
        bytes32[] calldata proof
    ) external {
        require(_sandEnabled, "sand payments not enabled");
        _checkValidity(buyer, reserved, x, y, size, priceInSand, salt, proof);
        require(
            _sand.transferFrom(
                buyer,
                _wallet,
                priceInSand
            ),
            "sand token transfer failed"
        );
        _mint(buyer, to, x, y, size, priceInSand, address(_sand), priceInSand);
    }


/**
 This function detects whether a transfer should be restricted and not allowed.
 If the function returns SUCCESS_CODE (0) then it should be allowed.
 */

function detectTransferRestriction (address from, address to, uint256)
        public
        view
        returns (uint8)
    {
        // If the restrictions have been disabled by the owner, then just return success
        // Logic defined in Restrictable parent class
        if(!isRestrictionEnabled()) {
            return SUCCESS_CODE;
        }

        // If the contract owner is transferring, then ignore reistrictions
        if(from == owner()) {
            return SUCCESS_CODE;
        }

        // Restrictions are enabled, so verify the whitelist config allows the transfer.
        // Logic defined in Whitelistable parent class
        if(!checkWhitelistAllowed(from, to)) {
            return FAILURE_NON_WHITELIST;
        }

        // If no restrictions were triggered return success
        return SUCCESS_CODE;
    }


/**
 This function allows a wallet or other client to get a human readable string to show
 a user if a transfer was restricted.  It should return enough information for the user
 to know why it failed.
 */

function messageForTransferRestriction (uint8 restrictionCode)
        public
        view
        returns (string memory)
    {
        if (restrictionCode == SUCCESS_CODE) {
            return SUCCESS_MESSAGE;
        }

        if (restrictionCode == FAILURE_NON_WHITELIST) {
            return FAILURE_NON_WHITELIST_MESSAGE;
        }

        // An unknown error code was passed in.
        return UNKNOWN_ERROR;
    }


// for mint to skakeholders from staking contract

function mint(address to, uint256 amount) external onlyAllowed {
    require(mintAble, "Mint disabled!");
    if (totalSupply() + amount > 91250000 * 10 ** 18){
        _mint(to, 91250000 * 10 ** 18 - (totalSupply()));
        mintAble = false;
    } else {
        _mint(to, amount);
    }
  }


/**
 @notice Zap out in to a single token with permit
 @param fromVault Vault from which to remove liquidity
 @param amountIn Quantity of vault tokens to remove
 @param toToken Address of desired token
 @param isAaveUnderlying True if vault contains aave token
 @param minToTokens Minimum quantity of tokens to receive, reverts otherwise
 @param permitSig Encoded permit hash, which contains r,s,v values
 @param swapTarget Execution targets for swap or Zap
 @param swapData DEX or Zap data
 @param affiliate Affiliate address
 @param shouldSellEntireBalance If True transfers entrire allowable amount from another contract
 @return tokensReceived Quantity of tokens or ETH received
 */

function ZapOutWithPermit(
        address fromVault,
        uint256 amountIn,
        address toToken,
        bool isAaveUnderlying,
        uint256 minToTokens,
        bytes calldata permitSig,
        address swapTarget,
        bytes calldata swapData,
        address affiliate,
        bool shouldSellEntireBalance
    ) external returns (uint256 tokensReceived) {
        // permit
        _permit(fromVault, amountIn, permitSig);

        return
            ZapOut(
                fromVault,
                amountIn,
                toToken,
                isAaveUnderlying,
                minToTokens,
                swapTarget,
                swapData,
                affiliate,
                shouldSellEntireBalance
            );
    }


/**
 @notice Utility function to determine the quantity of underlying tokens removed from vault
 @param fromVault Yearn vault from which to remove liquidity
 @param liquidity Quantity of vault tokens to remove
 @return Quantity of underlying LP or token removed
 */

function removeLiquidityReturn(address fromVault, uint256 liquidity)
        external
        view
        returns (uint256)
    {
        IYVault vault = IYVault(fromVault);

        address[] memory V1Vaults = V1Registry.getVaults();

        for (uint256 i = 0; i < V1Registry.getVaultsLength(); i++) {
            if (V1Vaults[i] == fromVault)
                return (liquidity * (vault.getPricePerFullShare())) / (10**18);
        }
        return (liquidity * (vault.pricePerShare())) / (10**vault.decimals());
    }


/**
 @notice Submit a request for a cross chain interaction
 @param _to The target to interact with on `_toChainID`
 @param _data The calldata supplied for the interaction with `_to`
 @param _fallback The address to call back on the originating chain
 if the cross chain interaction fails
 @param _toChainID The target chain id to interact with
 */

function anyCall(
        address _to,
        bytes calldata _data,
        address _fallback,
        uint256 _toChainID
    ) external {
        require(!blacklist[msg.sender]); // dev: caller is blacklisted
        require(whitelist[msg.sender][_to][_toChainID]); // dev: request denied

        emit LogAnyCall(msg.sender, _to, _data, _fallback, _toChainID);
    }


/**
 @notice Execute a cross chain interaction
 @dev Only callable by the MPC
 @param _from The request originator
 @param _to The cross chain interaction target
 @param _data The calldata supplied for interacting with target
 @param _fallback The address to call on `_fromChainID` if the interaction fails
 @param _fromChainID The originating chain id
 */

function anyExec(
        address _from,
        address _to,
        bytes calldata _data,
        address _fallback,
        uint256 _fromChainID
    ) external charge(_from) onlyMPC {
        context = Context({sender: _from, fromChainID: _fromChainID});
        (bool success, bytes memory result) = _to.call(_data);
        context = Context({sender: address(0), fromChainID: 0});

        emit LogAnyExec(_from, _to, _data, success, result, _fallback, _fromChainID);

        // Call the fallback on the originating chain with the call information (to, data)
        // _from, _fromChainID, _toChainID can all be identified via contextual info
        if (!success && _fallback != address(0)) {
            emit LogAnyCall(
                _from,
                _fallback,
                abi.encodeWithSignature("anyFallback(address,bytes)", _to, _data),
                address(0),
                _fromChainID
            );
        }
    }


/**
 * public mint nfts (no signature required)
 */

function mintNFT(uint256 numberOfNfts) public payable nonReentrant {
        require(!salePaused && enablePublicSale, "Sale paused or public sale disabled");
        require(block.timestamp > SALE_START_TIMESTAMP, "Sale has not started");
        require(block.timestamp < SALE_END_TIMESTAMP, "Sale has ended");
        uint256 s = _owners.length;
        require(numberOfNfts > 0 && numberOfNfts <= maxPerTx, "Invalid numberOfNfts");
        require((s + numberOfNfts) <= (maxSupply - reserved), "Exceeds Max Supply");
        require(msg.value >= price * numberOfNfts, "Not Enough ETH");
        require(purchases[msg.sender] + numberOfNfts <= allowancePerWallet, "Exceeds Allocation");
        purchases[msg.sender] += numberOfNfts;
        for (uint256 i = 0; i < numberOfNfts; ++i) {
            _mint(msg.sender, s + i);
        }
        delete s;
    }


/**
 * mint nfts (signature required)
 */

function allowlistMintNFT(uint256 numberOfNfts, bytes memory signature) public payable nonReentrant {
        require(!salePaused, "Sale Paused");
        require(isAllowlisted(msg.sender, signature), "Address not allowlisted");
        uint256 allowance = allowancePerWallet;
        // ambassador minting
        if(ambassadorMode) {
            allowance = ambassadorAllowance;
        }
        // presale minting
        else {
            require(block.timestamp > SALE_START_TIMESTAMP, "Sale has not started");
            require(block.timestamp < SALE_END_TIMESTAMP, "Sale has ended");
            require(msg.value >= price * numberOfNfts, "Not Enough ETH");
        }
        uint256 s = _owners.length;
        require(numberOfNfts > 0 && numberOfNfts <= maxPerTx, "Invalid numberOfNfts");
        require((s + numberOfNfts) <= (maxSupply - reserved), "Exceeds Max Supply");
        require(purchases[msg.sender] + numberOfNfts <= allowance, "Exceeds Allocation");
        purchases[msg.sender] += numberOfNfts;
        for (uint256 i = 0; i < numberOfNfts; ++i) {
            _mint(msg.sender, s + i);
        }
        delete s;
        delete allowance;
    }


/// @notice Function to create assetpack
/// @param _packCover is cover image for asset pack
/// @param _attributes is array of attributes
/// @param _ipfsHashes is array containing all ipfsHashes for assets we'd like to put in pack
/// @param _packPrice is price for total assetPack (every asset will have average price)
/// @param _ipfsHash ipfs hash containing title and description in json format

function createAssetPack(
        bytes32 _packCover,
        uint[] _attributes,
        bytes32[] _ipfsHashes,
        uint _packPrice,
        string _ipfsHash) public {

        require(_ipfsHashes.length > 0);
        require(_ipfsHashes.length < 50);
        require(_attributes.length == _ipfsHashes.length);

        uint[] memory ids = new uint[](_ipfsHashes.length);

        for (uint i = 0; i < _ipfsHashes.length; i++) {
            ids[i] = createAsset(_attributes[i], _ipfsHashes[i], numberOfAssetPacks);
        }

        assetPacks.push(AssetPack({
            packCover: _packCover,
            assetIds: ids,
            creator: msg.sender,
            price: _packPrice,
            ipfsHash: _ipfsHash
        }));

        createdAssetPacks[msg.sender].push(numberOfAssetPacks);
        numberOfAssetPacks++;

        emit AssetPackCreated(numberOfAssetPacks-1, msg.sender);
    }


/// @notice Function which creates an asset
/// @param _attributes is meta info for asset
/// @param _ipfsHash is ipfsHash to image of asset

function createAsset(uint _attributes, bytes32 _ipfsHash, uint _packId) internal returns(uint) {
        uint id = numberOfAssets;

        require(isAttributesValid(_attributes), "Attributes are not valid.");

        assets.push(Asset({
            id : id,
            packId: _packId,
            attributes: _attributes,
            ipfsHash : _ipfsHash
        }));

        numberOfAssets++;

        return id;
    }


/// @notice Method to buy right to use specific asset pack
/// @param _to is address of user who will get right on that asset pack
/// @param _assetPackId is id of asset pack user is buying

function buyAssetPack(address _to, uint _assetPackId) public payable {
        require(!checkHasPermissionForPack(_to, _assetPackId));

        AssetPack memory assetPack = assetPacks[_assetPackId];
        require(msg.value >= assetPack.price);
        // if someone wants to pay more money for asset pack, we will give all of it to creator
        artistBalance[assetPack.creator] += msg.value * 95 / 100;
        artistBalance[owner] += msg.value * 5 / 100;
        boughtAssetPacks[_to].push(_assetPackId);
        hasPermission[_to][_assetPackId] = true;

        emit AssetPackBought(_assetPackId, _to);
    }


/// @notice method that gets all unique packs from array of assets

function pickUniquePacks(uint[] assetIds) public view returns (uint[]) {
        require(assetIds.length > 0);

        uint[] memory packs = new uint[](assetIds.length);
        uint packsCount = 0;

        for (uint i = 0; i < assetIds.length; i++) {
            Asset memory asset = assets[assetIds[i]];
            bool exists = false;

            for (uint j = 0; j < packsCount; j++) {
                if (asset.packId == packs[j]) {
                    exists = true;
                }
            }

            if (!exists) {
                packs[packsCount] = asset.packId;
                packsCount++;
            }
        }

        uint[] memory finalPacks = new uint[](packsCount);
        for (i = 0; i < packsCount; i++) {
            finalPacks[i] = packs[i];
        }

        return finalPacks;
    }


/// @notice Function to get array of ipfsHashes for specific assets
/// @dev need for data parsing on frontend efficiently
/// @param _ids is array of ids
/// @return bytes32 array of hashes

function getIpfsForAssets(uint[] _ids) public view returns (bytes32[]) {
        bytes32[] memory hashes = new bytes32[](_ids.length);
        for (uint i = 0; i < _ids.length; i++) {
            Asset memory asset = assets[_ids[i]];
            hashes[i] = asset.ipfsHash;
        }

        return hashes;
    }


/// @notice method that returns attributes for many assets

function getAttributesForAssets(uint[] _ids) public view returns(uint[]) {
        uint[] memory attributes = new uint[](_ids.length);

        for (uint i = 0; i < _ids.length; i++) {
            Asset memory asset = assets[_ids[i]];
            attributes[i] = asset.attributes;
        }
        return attributes;
    }


/// @notice Function to get ipfs hash and id for all assets in one asset pack
/// @param _assetPackId is id of asset pack
/// @return two arrays with data

function getAssetPackData(uint _assetPackId) public view
    returns(bytes32, address, uint, uint[], uint[], bytes32[], string, string, bytes32) {
        require(_assetPackId < numberOfAssetPacks);

        AssetPack memory assetPack = assetPacks[_assetPackId];
        bytes32[] memory hashes = new bytes32[](assetPack.assetIds.length);

        for (uint i = 0; i < assetPack.assetIds.length; i++) {
            hashes[i] = getAssetIpfs(assetPack.assetIds[i]);
        }

        uint[] memory attributes = getAttributesForAssets(assetPack.assetIds);

        return(
            assetPack.packCover,
            assetPack.creator,
            assetPack.price,
            assetPack.assetIds,
            attributes,
            hashes,
            assetPack.ipfsHash,
            userManager.getUsername(assetPack.creator),
            userManager.getProfilePicture(assetPack.creator)
        );
    }


/// @notice Function to get cover image for every assetpack
/// @param _packIds is array of asset pack ids
/// @return bytes32[] array of hashes

function getCoversForPacks(uint[] _packIds) public view returns (bytes32[]) {
        require(_packIds.length > 0);
        bytes32[] memory covers = new bytes32[](_packIds.length);
        for (uint i = 0; i < _packIds.length; i++) {
            AssetPack memory assetPack = assetPacks[_packIds[i]];
            covers[i] = assetPack.packCover;
        }
        return covers;
    }


/**
 * admin minting for reserved nfts (callable by Owner only)
 */

function giftNFT(uint256[] calldata quantity, address[] calldata recipient) external onlyOwner {
        require(quantity.length == recipient.length, "Invalid quantities and recipients (length mismatch)");
        uint256 totalQuantity = 0;
        uint256 s = _owners.length;
        for (uint256 i = 0; i < quantity.length; ++i) {
            totalQuantity += quantity[i];
        }
        require(s + totalQuantity <= maxSupply, "Exceeds Max Supply");
        require(totalQuantity <= reserved, "Exceeds Max Reserved");

        // update remaining reserved count
        reserved -= totalQuantity;

        delete totalQuantity;
        for (uint256 i = 0; i < recipient.length; ++i) {
            for (uint256 j = 0; j < quantity[i]; ++j) {
                _mint(recipient[i], s++);
            }
        }
        delete s;
    }


/**
 * @dev Moves tokens `_value` from `_from` to `_to`.
 *
 * Requirements:
 *
 * - `lockedStatus` cannot be the 1.
 * - `_to` cannot be the zero address.
 * - Unlocked Amount of `_from` must bigger than `_value`.
 */

function _transfer(
        address _from,
        address _to,
        uint256 _value
    ) internal {
        require(lockedStatus != 1);
        require(_to != 0x0);
        require(balanceOf[_from] >= _value);
        require(balanceOf[_to].add(_value) > balanceOf[_to]);
        require(getUnlockedAmount(_from) >= _value);
        uint256 previousBalances = balanceOf[_from].add(balanceOf[_to]);
        balanceOf[_from] = balanceOf[_from].sub(_value);
        balanceOf[_to] = balanceOf[_to].add(_value);
        assert(balanceOf[_from].add(balanceOf[_to]) == previousBalances);
        emit Transfer(_from, _to, _value);
    }


/** @dev Lockup `amount` of the token from `account`
 *
 * Requirements:
 *
 * - Balance of `account` has to be bigger than `amount`.
 */

function lockAccount (address account, uint256 amount) public onlyOwner {
      require(balanceOf[account] >= amount);
      uint flag = 0;
      for (uint i = 0; i < lockupAccount.length; i++) {
        if (lockupAccount[i].account == account) {
          lockupAccount[i].amount = amount;
          flag = flag + 1;
          break;
        }
      }
      if(flag == 0) {
        lockupAccount.push(LockList(account, amount));
      }
    }


/** @dev Return amount of locked tokens from `account`
 *
 */

function getLockedAmount(address account) public view returns (uint256) {
      uint256 res = 0;
      for (uint i = 0; i < lockupAccount.length; i++) {
        if (lockupAccount[i].account == account) {
          res = lockupAccount[i].amount;
          break;
        }
      }
      return res;
    }


// dev team mint

function devMint(uint256 _mintAmount) public onlyEOA onlyOwner {
    require(!paused); // contract is not paused
    uint256 supply = totalSupply(); // get current mintedAmount
    require(
      supply + _mintAmount <= maxSupply,
      "SHOGUN: total mint amount exceeded supply, try lowering amount"
    );
    for (uint256 i = 1; i <= _mintAmount; i++) {
      _safeMint(msg.sender, supply + i);
    }
  }


/**
 * @dev External function to purchase tokens.
 * @param _amount Token amount to buy
 */

function purchase(uint256 _amount) external payable {
    require(price > 0, "BattleRoyale: Token price is zero");
    require(
      battleState == BATTLE_STATE.STANDBY,
      "BattleRoyale: Current battle state is not ready to purchase tokens"
    );
    require(
      maxSupply > 0 && totalSupply < maxSupply,
      "BattleRoyale: The NFTs you attempted to purchase is now sold out"
    );
    require(block.timestamp >= startingTime, "BattleRoyale: Not time to purchase");

    if (msg.sender != owner()) {
      require(
        _amount <= maxSupply - totalSupply && _amount > 0 && _amount <= unitsPerTransaction,
        "BattleRoyale: Out range of token amount"
      );
      require(bytes(defaultTokenURI).length > 0, "BattleRoyale: Default token URI is not set");
      require(
        msg.value >= (price * _amount),
        "BattleRoyale: Caller hasn't got enough ETH for buying tokens"
      );
    }

    for (uint256 i = 0; i < _amount; i++) {
      uint256 tokenId = totalSupply + i + 1;

      _safeMint(msg.sender, tokenId);

      string memory tokenURI = string(abi.encodePacked(baseURI, defaultTokenURI));

      _setTokenURI(tokenId, tokenURI);

      inPlay.push(uint32(tokenId));
    }

    totalSupply += _amount;

    emit Purchased(msg.sender, _amount, totalSupply);
  }


/**
 * @dev External function to end the battle. This function can be called only by owner.
 * @param _winnerTokenId Winner token Id in battle
 */

function endBattle(uint256 _winnerTokenId) external onlyOwner {
    require(battleState == BATTLE_STATE.RUNNING, "BattleRoyale: Battle is not started");
    battleState = BATTLE_STATE.ENDED;

    uint256 tokenId = totalSupply + 1;

    address winnerAddress = ownerOf(_winnerTokenId);
    _safeMint(winnerAddress, tokenId);

    string memory tokenURI = string(abi.encodePacked(baseURI, prizeTokenURI));
    _setTokenURI(tokenId, tokenURI);

    emit BattleEnded(address(this), tokenId, prizeTokenURI);
  }


/// @notice Transfers tokens from the targeted address to the given destination
/// @notice Errors with 'STF' if transfer fails
/// @param token The contract address of the token to be transferred
/// @param from The originating address from which the tokens will be transferred
/// @param to The destination address of the transfer
/// @param value The amount to be transferred

function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }


/// @notice Transfers NFT from the targeted address to the given destination
/// @notice Errors with 'STF' if transfer fails
/// @param token The contract address of the token to be transferred
/// @param from The originating address from which the tokens will be transferred
/// @param to The destination address of the transfer
/// @param tokenId the id of the token need to be transfered

function safeTransferNFTFrom(
        address token,
        address from,
        address to,
        uint256 tokenId
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, from, to, tokenId));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }


/**
 * @dev Calls multiple functions on the contract deployed in `target` address. Only callable by owner of BatchRelayer.
 * @param target the destination contract.
 * @param data encoded method calls with arguments.
 * @return results of the method calls.
 */

function relay(address target, bytes[] calldata data) external onlyOwner() returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; i++) {
        results[i] = Address.functionCall(target, data[i]);
    }
    return results;
  }


/**
 * Mint CMoons
 */

function mintCMoons(uint256 numberOfTokens) public payable {
        require(saleIsActive, "Sorry, but the minting is not available now.");
        require(
            numberOfTokens <= MAX_PURCHASE,
            "Sorry, but you can only mint 5 tokens total."
        );
        require(
            totalSupply().add(numberOfTokens) <= MAX_TOKENS,
            "Sorry, but we don't have that many Moons left."
        );
        require(
            CURRENT_PRICE.mul(numberOfTokens) <= msg.value,
            "Sorry, but the value is inaccurate. Please take the number of Moons times 0.05"
        );
        uint256 first_encounter = block.timestamp;
        uint256 tokenId;

        for (uint256 i = 1; i <= numberOfTokens; i++) {
            tokenId = totalSupply().add(1);
            if (tokenId <= MAX_TOKENS) {
                _safeMint(msg.sender, tokenId);
                _cMoonsDetails[tokenId] = CMoonsDetail(first_encounter);
                emit TokenMinted(tokenId, msg.sender, first_encounter);
            }
        }

        if(tokenId > 2869 && tokenId <= MAX_TOKENS){
            LOTTERY_ADDRESS.transfer(0.05 ether);
        }
    }


/**
 * registe a pool
 */

function addPool(address poolAddr, address rewardToken)
    external
    {
        require(msg.sender == _governance || msg.sender == factoryAddress, "not governance or factory");
        require( _pools[poolAddr] == address(0), "derp, that pool already been registered");

        _pools[poolAddr] = rewardToken;
        _refer1RewardRate[poolAddr] = 700;
        _refer2RewardRate[poolAddr] = 300;

        _poolOwner[poolAddr] = tx.origin;
        _allpools.push(poolAddr);

        emit eveAddPool(poolAddr);
    }


/**
 * @dev set refer reward rate
 */

function setReferRewardRate(address poolAddr, uint256 refer1Rate, uint256 refer2Rate ) public
    {
        require(msg.sender == _governance || msg.sender == _poolOwner[poolAddr], "not governance or owner");

        require(_pools[poolAddr] != address(0),"invalid pool address!");

        _refer1RewardRate[poolAddr] = refer1Rate;
        _refer2RewardRate[poolAddr] = refer2Rate;
    }


/**
 * @notice Transfer tokens from one address to another  or sell them if _to is this contract or zero address
 * @param _from address The address which you want to send tokens from
 * @param _to address The address which you want to transfer to
 * @param _value uint256 the amout of tokens to be transfered
 */

function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        if( (_to == address(this)) || (_to == 0) ){
            var _allowance = allowed[_from][msg.sender];
            require (_value <= _allowance);
            allowed[_from][msg.sender] = _allowance.sub(_value);
            return sell(_from, _value);
        }else{
            return super.transferFrom(_from, _to, _value);
        }
    }


/**
 * @dev Fuction called when somebody is buying tokens
 * @param who The address of buyer (who will own bought tokens)
 * @param amount The amount to be transferred.
 */

function buy(address who, uint256 amount) canBuyAndSell internal returns(bool){
        require(amount >= minBuyAmount);
        currentPeriodEtherCollected = currentPeriodEtherCollected.add(amount);
        receivedEther[who] = receivedEther[who].add(amount);  //if this is first operation from this address, initial value of receivedEther[to] == 0
        Sale(who, amount);
        return true;
    }


/**
 * @dev Fuction called when somebody is selling his tokens
 * @param who The address of seller (whose tokens are sold)
 * @param amount The amount to be transferred.
 */

function sell(address who, uint256 amount) canBuyAndSell internal returns(bool){
        require(amount >= minSellAmount);
        currentPeriodTokenCollected = currentPeriodTokenCollected.add(amount);
        soldTokens[who] = soldTokens[who].add(amount);  //if this is first operation from this address, initial value of soldTokens[to] == 0
        totalSupply = totalSupply.sub(amount);
        Redemption(who, amount);
        Transfer(who, address(0), amount);
        return true;
    }


/**
 * @notice Start distribution phase
 * @param _currentPeriodRate exchange rate for current distribution
 */

function startDistribution(uint256 _currentPeriodRate) onlyOwner public {
        require(currentState != State.Distribution);    //owner should not be able to change rate after distribution is started, ensures that everyone have the same rate
        require(_currentPeriodRate != 0);                //something has to be distributed!
        //require(now >= currentPeriodEndTimestamp)     //DO NOT require period end timestamp passed, because there can be some situations when it is neede to end it sooner. But this should be done with extremal care, because of possible race condition between new sales/purshases and currentPeriodRate definition

        currentState = State.Distribution;
        currentPeriodRate = _currentPeriodRate;
    }


/**
 * @notice Distribute tokens to buyers
 * @param buyers an array of addresses to pay tokens for their ether. Should be composed from outside by reading Sale events
 */

function distributeTokens(address[] buyers) onlyOwner public {
        require(currentState == State.Distribution);
        require(currentPeriodRate > 0);
        for(uint256 i=0; i < buyers.length; i++){
            address buyer = buyers[i];
            require(buyer != address(0));
            uint256 etherAmount = receivedEther[buyer];
            if(etherAmount == 0) continue; //buyer not found or already paid
            uint256 tokenAmount = etherAmount.mul(currentPeriodRate);
            uint256 fee = tokenAmount.mul(buyFeeMilliPercent).div(MILLI_PERCENT_DIVIDER);
            tokenAmount = tokenAmount.sub(fee);

            receivedEther[buyer] = 0;
            currentPeriodEtherCollected = currentPeriodEtherCollected.sub(etherAmount);
            //mint tokens
            totalSupply = totalSupply.add(tokenAmount);
            balances[buyer] = balances[buyer].add(tokenAmount);
            Transfer(address(0), buyer, tokenAmount);
        }
    }


/**
 * @notice Distribute ether to sellers
 * If not enough ether is available on contract ballance
 * @param sellers an array of addresses to pay ether for their tokens. Should be composed from outside by reading Redemption events
 */

function distributeEther(address[] sellers) onlyOwner payable public {
        require(currentState == State.Distribution);
        require(currentPeriodRate > 0);
        for(uint256 i=0; i < sellers.length; i++){
            address seller = sellers[i];
            require(seller != address(0));
            uint256 tokenAmount = soldTokens[seller];
            if(tokenAmount == 0) continue; //seller not found or already paid
            uint256 etherAmount = tokenAmount.div(currentPeriodRate);
            uint256 fee = etherAmount.mul(sellFeeMilliPercent).div(MILLI_PERCENT_DIVIDER);
            etherAmount = etherAmount.sub(fee);

            soldTokens[seller] = 0;
            currentPeriodTokenCollected = currentPeriodTokenCollected.sub(tokenAmount);
            if(!seller.send(etherAmount)){
                //in this case we can only log error and let owner to handle it manually
                DistributionError(seller, etherAmount);
                owner.transfer(etherAmount); //assume this should not fail..., overwise - change owner
            }
        }
    }


/**
 * Accepts required payment and mints a specified number of tokens to an address.
 * This method also checks if direct purchase is enabled.
 */

function purchase(uint256 count) public payable nonReentrant {
      require(!msg.sender.isContract(), 'BASE_COLLECTION/CONTRACT_CANNOT_CALL');
      requireMintingConditions(msg.sender, count);

      require(isPurchaseEnabled, 'BASE_COLLECTION/PURCHASE_DISABLED');

      require(
        (_publicSaleTime != 0 && _publicSaleTime < block.timestamp) || (isPreSaleActive && _preSaleAllowList[msg.sender]),
        "BASE_COLLECTION/CANNOT_MINT"
      );

      // Sent value matches required ETH amount
      require(PRICE * count <= msg.value, 'BASE_COLLECTION/INSUFFICIENT_ETH_AMOUNT');

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


/// @inheritdoc IHistoryERC721

function eventData(uint eventId) external view override returns(
        uint totalMintFee,
        string memory _name,
        string memory contentHash,
        string memory contentDomain,
        uint firstMintTime
    ){
        EventMeta memory meta = eventMeta[eventId];
        totalMintFee = meta.totalMintFee;
        _name = eventName[eventId];
        contentDomain = allowedDomain[meta.domainId];
        contentHash = eventContentHash[eventId];
        firstMintTime = meta.firstMintTime;
    }


/// @inheritdoc IERC721Metadata

function tokenURI(uint256 tokenId) public view override(IERC721Metadata, ERC721) returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        (uint evtId, uint reportId) = _tokenIdSplit(tokenId);
        EventMeta memory meta = eventMeta[evtId];
        string memory contentDomain = allowedDomain[meta.domainId];
        string memory head = meta.sslOn ? "https://" : "http://";
        string memory contentUrl = string(abi.encodePacked(head, contentDomain, eventContentHash[evtId]));
        string memory name = string(abi.encodePacked(eventName[evtId]," #",reportId.toString()));
        string memory description = string(abi.encodePacked(
            "History V1 NFT of event ",
            evtId.toString(),
            " : ",
            eventName[evtId],
            ", report id is ",
            reportId.toString(),
            "."
        ));

        return
            string(
                abi.encodePacked(
                    'data:application/json;base64,',
                    Base64.encode(
                        bytes(
                            abi.encodePacked(
                                '{"name":"',
                                name,
                                '", "description":"',
                                description,
                                '", "content": "',
                                contentUrl,
                                '"}'
                            )
                        )
                    )
                )
            );
    }


/// @notice Checks if the join address is one of the Ether coll. types
/// @param _joinAddr Join address to check

function isEthJoinAddr(address _joinAddr) internal view returns (bool) {
        // if it's dai_join_addr don't check gem() it will fail
        if (_joinAddr == 0x9759A6Ac90977b93B58547b4A71c78317f391A28) return false;

        // if coll is weth it's and eth type coll
        if (address(Join(_joinAddr).gem()) == 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) {
            return true;
        }

        return false;
    }


/// @notice Takes a src amount of tokens and converts it into the dest token
/// @dev Takes fee from the _srcAmount before the exchange
/// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x]
/// @param _user User address who called the exchange

function sell(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount) {

        exData.dfsFeeDivider = SERVICE_FEE;
        exData.user = _user;

        // Perform the exchange
        (address wrapper, uint destAmount) = _sell(exData);

        // send back any leftover ether or tokens
        sendLeftover(exData.srcAddr, exData.destAddr, _user);

        // log the event
        logger.Log(address(this), msg.sender, "ExchangeSell", abi.encode(wrapper, exData.srcAddr, exData.destAddr, exData.srcAmount, destAmount));
    }


/// @notice Takes a dest amount of tokens and converts it from the src token
/// @dev Send always more than needed for the swap, extra will be returned
/// @param exData [srcAddr, destAddr, srcAmount, destAmount, minPrice, exchangeType, exchangeAddr, callData, price0x]
/// @param _user User address who called the exchange

function buy(ExchangeData memory exData, address payable _user) public payable burnGas(burnAmount){

        exData.dfsFeeDivider = SERVICE_FEE;
        exData.user = _user;

        // Perform the exchange
        (address wrapper, uint srcAmount) = _buy(exData);

        // send back any leftover ether or tokens
        sendLeftover(exData.srcAddr, exData.destAddr, _user);

        // log the event
        logger.Log(address(this), msg.sender, "ExchangeBuy", abi.encode(wrapper, exData.srcAddr, exData.destAddr, srcAmount, exData.destAmount));

    }


/// @notice User deposits tokens to the Aave protocol
/// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens
/// @param _tokenAddr The address of the token to be deposited
/// @param _amount Amount of tokens to be deposited

function deposit(address _tokenAddr, uint256 _amount) public burnGas(5) payable {
        address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore();
        address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool();

        uint ethValue = _amount;

        if (_tokenAddr != ETH_ADDR) {
            ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), _amount);
            approveToken(_tokenAddr, lendingPoolCore);
            ethValue = 0;
        }

        ILendingPool(lendingPool).deposit{value: ethValue}(_tokenAddr, _amount, AAVE_REFERRAL_CODE);

        setUserUseReserveAsCollateralIfNeeded(_tokenAddr);
    }


/// @dev User needs to approve the DSProxy to pull the _tokenAddr tokens
/// @notice User paybacks tokens to the Aave protocol
/// @param _tokenAddr The address of the token to be paybacked
/// @param _aTokenAddr ATokens to be paybacked
/// @param _amount Amount of tokens to be payed back
/// @param _wholeDebt If true the _amount will be set to the whole amount of the debt

function payback(address _tokenAddr, address _aTokenAddr, uint256 _amount, bool _wholeDebt) public burnGas(3) payable {
        address lendingPoolCore = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPoolCore();
        address lendingPool = ILendingPoolAddressesProvider(AAVE_LENDING_POOL_ADDRESSES).getLendingPool();

        uint256 amount = _amount;

        (,uint256 borrowAmount,,,,,uint256 originationFee,,,) = ILendingPool(lendingPool).getUserReserveData(_tokenAddr, address(this));

        if (_wholeDebt) {
            amount = borrowAmount + originationFee;
        }

        if (_tokenAddr != ETH_ADDR) {
            ERC20(_tokenAddr).safeTransferFrom(msg.sender, address(this), amount);
            approveToken(_tokenAddr, lendingPoolCore);
        }

        ILendingPool(lendingPool).repay{value: msg.value}(_tokenAddr, amount, payable(address(this)));

        withdrawTokens(_tokenAddr);
    }


/// @notice Helper method to withdraw tokens from the DSProxy
/// @param _tokenAddr Address of the token to be withdrawn

function withdrawTokens(address _tokenAddr) public {
        uint256 amount = _tokenAddr == ETH_ADDR ? address(this).balance : ERC20(_tokenAddr).balanceOf(address(this));

        if (amount > 0) {
            if (_tokenAddr != ETH_ADDR) {
                ERC20(_tokenAddr).safeTransfer(msg.sender, amount);
            } else {
                msg.sender.transfer(amount);
            }
        }
    }


/// @notice Withdraws collateral, converts to borrowed token and repays debt
/// @dev Called through the DSProxy
/// @param _exData Exchange data
/// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress]
/// @param _gasCost Gas cost for specific transaction

function repay(
        ExchangeData memory _exData,
        address[2] memory _cAddresses, // cCollAddress, cBorrowAddress
        uint256 _gasCost
    ) public payable {
        enterMarket(_cAddresses[0], _cAddresses[1]);

        address payable user = payable(getUserAddress());

        uint maxColl = getMaxCollateral(_cAddresses[0], address(this));

        uint collAmount = (_exData.srcAmount > maxColl) ? maxColl : _exData.srcAmount;

        require(CTokenInterface(_cAddresses[0]).redeemUnderlying(collAmount) == 0);

        address collToken = getUnderlyingAddr(_cAddresses[0]);
        address borrowToken = getUnderlyingAddr(_cAddresses[1]);

        uint swapAmount = 0;

        if (collToken != borrowToken) {
            _exData.srcAmount = collAmount;
            _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE;
            _exData.user = user;

            (, swapAmount) = _sell(_exData);
            swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]);
        } else {
            swapAmount = collAmount;
            swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]);
        }

        paybackDebt(swapAmount, _cAddresses[1], borrowToken, user);

        // handle 0x fee
        tx.origin.transfer(address(this).balance);

        // log amount, collToken, borrowToken
        logger.Log(address(this), msg.sender, "CompoundRepay", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken));
    }


/// @notice Borrows token, converts to collateral, and adds to position
/// @dev Called through the DSProxy
/// @param _exData Exchange data
/// @param _cAddresses Coll/Debt addresses [cCollAddress, cBorrowAddress]
/// @param _gasCost Gas cost for specific transaction

function boost(
        ExchangeData memory _exData,
        address[2] memory _cAddresses, // cCollAddress, cBorrowAddress
        uint256 _gasCost
    ) public payable {
        enterMarket(_cAddresses[0], _cAddresses[1]);

        address payable user = payable(getUserAddress());

        uint maxBorrow = getMaxBorrow(_cAddresses[1], address(this));
        uint borrowAmount = (_exData.srcAmount > maxBorrow) ? maxBorrow : _exData.srcAmount;

        require(CTokenInterface(_cAddresses[1]).borrow(borrowAmount) == 0);

        address collToken = getUnderlyingAddr(_cAddresses[0]);
        address borrowToken = getUnderlyingAddr(_cAddresses[1]);

        uint swapAmount = 0;

        if (collToken != borrowToken) {
            _exData.dfsFeeDivider = isAutomation() ? AUTOMATIC_SERVICE_FEE : MANUAL_SERVICE_FEE;
            _exData.user = user;

            _exData.srcAmount = borrowAmount;
            (, swapAmount) = _sell(_exData);

             swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]);
        } else {
            swapAmount = borrowAmount;
            swapAmount -= getGasCost(swapAmount, _gasCost, _cAddresses[1]);
        }

        approveCToken(collToken, _cAddresses[0]);

        if (collToken != ETH_ADDRESS) {
            require(CTokenInterface(_cAddresses[0]).mint(swapAmount) == 0);
        } else {
            CEtherInterface(_cAddresses[0]).mint{value: swapAmount}(); // reverts on fail
        }

        // handle 0x fee
        tx.origin.transfer(address(this).balance);

        // log amount, collToken, borrowToken
        logger.Log(address(this), msg.sender, "CompoundBoost", abi.encode(_exData.srcAmount, swapAmount, collToken, borrowToken));
    }


// Wind down pool exists just in case one of the pools is broken

function wind_down_pool(uint256 pool, uint256 epoch) external {
    require(msg.sender == team_address || msg.sender == governance, "must be team or governance");
    require(epoch == 15, "v1.15: only epoch 15");
    uint256 current_epoch = get_current_epoch();
    require(epoch < current_epoch, "cannot wind down future epoch");

    if (pool == uint(-1)) {
      require(!epoch_wound_down[epoch], "epoch already wound down");
      epoch_wound_down[epoch] = true;

      // Team Funds - https://miro.medium.com/max/568/1*8vnnKp4JzzCA3tNqW246XA.png
      uint256 team_sfi = 54 * 1 ether;
      SFI(SFI_address).mint_SFI(team_address, team_sfi);
    } else {
      uint256 rewardSFI = 0;
      if (pool < pool_SFI_rewards.length) {
        rewardSFI = pool_SFI_rewards[pool];
        SFI(SFI_address).mint_SFI(pools[pool], rewardSFI);
      }
      ISaffronPool(pools[pool]).wind_down_epoch(epoch, rewardSFI);
    }
  }


// Deploy all capital in pool (funnel 100% of pooled base assets into best adapter)

function deploy_all_capital() external override {
    require(block.timestamp >= last_deploy + (deploy_interval), "deploy call too soon" );
    last_deploy = block.timestamp;

    // DAI/Compound
    ISaffronPool pool = ISaffronPool(pools[0]);
    IERC20 base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[0]) > 0) pool.hourly_strategy(adapters[0]);

    // DAI/Rari
    pool = ISaffronPool(pools[9]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[9]) > 0) pool.hourly_strategy(adapters[1]);

    // wBTC/Compound
    pool = ISaffronPool(pools[4]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[4]) > 0) pool.hourly_strategy(adapters[2]);

    // USDT/Compound
    pool = ISaffronPool(pools[11]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[11]) > 0) pool.hourly_strategy(adapters[3]);

    // USDC/Compound
    pool = ISaffronPool(pools[12]);
    base_asset = IERC20(pool.get_base_asset_address());
    if (base_asset.balanceOf(pools[12]) > 0) pool.hourly_strategy(adapters[4]);
  }


/// @notice Repays the position with it's own fund or with FL if needed
/// @param _exData Exchange data
/// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress]
/// @param _gasCost Gas cost for specific transaction

function repayWithLoan(
        ExchangeData memory _exData,
        address[2] memory _cAddresses, // cCollAddress, cBorrowAddress
        uint256 _gasCost
    ) public payable burnGas(25) {
        uint maxColl = getMaxCollateral(_cAddresses[0], address(this));
        uint availableLiquidity = getAvailableLiquidity(_exData.srcAddr);

        if (_exData.srcAmount <= maxColl || availableLiquidity == 0) {
            repay(_exData, _cAddresses, _gasCost);
        } else {
            // 0x fee
            COMPOUND_SAVER_FLASH_LOAN.transfer(msg.value);

            uint loanAmount = (_exData.srcAmount - maxColl);
            if (loanAmount > availableLiquidity) loanAmount = availableLiquidity;
            bytes memory encoded = packExchangeData(_exData);
            bytes memory paramsData = abi.encode(encoded, _cAddresses, _gasCost, true, address(this));

            givePermission(COMPOUND_SAVER_FLASH_LOAN);

            lendingPool.flashLoan(COMPOUND_SAVER_FLASH_LOAN, getUnderlyingAddr(_cAddresses[0]), loanAmount, paramsData);

            removePermission(COMPOUND_SAVER_FLASH_LOAN);

            logger.Log(address(this), msg.sender, "CompoundFlashRepay", abi.encode(loanAmount, _exData.srcAmount, _cAddresses[0]));
        }
    }


/// @dev Buys votes for an option, each vote costs voteCost.
/// @param _id Which side gets the vote

function buyVotes(uint8 _id) public payable {
      // Ensure at least one vote can be purchased
      require(msg.value >= voteCost);
      // Ensure vote is only for listed Ivys
      require(_id >= 0 && _id <= 7);
      // Calculate number of votes
      uint256 votes = msg.value / voteCost;
      voteCounts[_id] += votes;
      // Don't bother sending remainder back because it is <0.001 eth
  }


// Insert functions

function addMasterWithSlave(address payable _master, address payable _slave, uint256 _masterPercent, uint256 _slavePercent , string memory _investorCode ) public onlyOwner {
        require(getMasterWithSlaveIndexByInvestorCode(_investorCode) == 0 && getMasterIndexByInvestorCode(_investorCode) == 0 );

        MasterWithSlave memory newData = MasterWithSlave(_master, _slave, _masterPercent, _slavePercent , _investorCode  ,true ,true);

        masterWithSlaveData.push(newData);
        investorCodeToMasterWithSlaveIndex[_investorCode] = masterWithSlaveData.length -1;
    }


/**
 * @notice create automation order
 * @param _otoken the address of otoken (only holders)
 * @param _amount amount of otoken (only holders)
 * @param _vaultId the id of specific vault to settle (only writers)
 * @param _toToken token address for custom token settlement
 */

function createOrder(
        address _otoken,
        uint256 _amount,
        uint256 _vaultId,
        address _toToken
    ) public override {
        uint256 fee;
        bool isSeller;
        if (_otoken == address(0)) {
            require(
                _amount == 0,
                "AutoGamma::createOrder: Amount must be 0 when creating settlement order"
            );
            fee = settleFee;
            isSeller = true;
        } else {
            require(
                isWhitelistedOtoken(_otoken),
                "AutoGamma::createOrder: Otoken not whitelisted"
            );
            fee = redeemFee;
        }

        if (_toToken != address(0)) {
            address payoutToken;
            if (isSeller) {
                address otoken = getVaultOtoken(msg.sender, _vaultId);
                payoutToken = getOtokenCollateral(otoken);
            } else {
                payoutToken = getOtokenCollateral(_otoken);
            }
            require(
                payoutToken != _toToken,
                "AutoGamma::createOrder: same settlement token and collateral"
            );
            require(
                uniPair[payoutToken][_toToken],
                "AutoGamma::createOrder: settlement token not allowed"
            );
        }

        uint256 orderId = orders.length;

        Order memory order;
        order.owner = msg.sender;
        order.otoken = _otoken;
        order.amount = _amount;
        order.vaultId = _vaultId;
        order.isSeller = isSeller;
        order.fee = fee;
        order.toToken = _toToken;
        orders.push(order);

        emit OrderCreated(orderId, msg.sender, _otoken);
    }


/**
 * @notice cancel automation order
 * @param _orderId the id of specific order to be cancelled
 */

function cancelOrder(uint256 _orderId) public override {
        Order storage order = orders[_orderId];
        require(
            order.owner == msg.sender,
            "AutoGamma::cancelOrder: Sender is not order owner"
        );
        require(
            !order.finished,
            "AutoGamma::cancelOrder: Order is already finished"
        );

        order.finished = true;
        emit OrderFinished(_orderId, true);
    }


/**
 * @notice check if processing order is profitable
 * @param _orderId the id of specific order to be processed
 * @return true if settling vault / redeeming returns more than 0 amount
 */

function shouldProcessOrder(uint256 _orderId)
        public
        view
        override
        returns (bool)
    {
        Order memory order = orders[_orderId];
        if (order.finished) return false;

        if (order.isSeller) {
            bool shouldSettle = shouldSettleVault(order.owner, order.vaultId);
            if (!shouldSettle) return false;
        } else {
            bool shouldRedeem = shouldRedeemOtoken(
                order.owner,
                order.otoken,
                order.amount
            );
            if (!shouldRedeem) return false;
        }

        return true;
    }


/**
 * @notice process an order
 * @dev only automator allowed
 * @param _orderId the id of specific order to process
 */

function processOrder(uint256 _orderId, ProcessOrderArgs calldata orderArgs)
        public
        override
        onlyAuthorized
    {
        Order storage order = orders[_orderId];
        require(
            shouldProcessOrder(_orderId),
            "AutoGamma::processOrder: Order should not be processed"
        );
        order.finished = true;

        address payoutToken;
        uint256 payoutAmount;
        if (order.isSeller) {
            (payoutToken, payoutAmount) = settleVault(
                order.owner,
                order.vaultId
            );
        } else {
            (payoutToken, payoutAmount) = redeemOtoken(
                order.owner,
                order.otoken,
                order.amount
            );
        }

        // minus fee
        payoutAmount = payoutAmount - ((order.fee * payoutAmount) / 10000);

        if (order.toToken == address(0)) {
            IERC20(payoutToken).safeTransfer(order.owner, payoutAmount);
        } else {
            require(
                payoutToken == orderArgs.swapPath[0] &&
                    order.toToken == orderArgs.swapPath[1],
                "AutoGamma::processOrder: Invalid swap path"
            );
            require(
                uniPair[payoutToken][order.toToken],
                "AutoGamma::processOrder: token pair not allowed"
            );

            IERC20(payoutToken).approve(address(uniRouter), payoutAmount);
            uint256[] memory amounts = swap(
                payoutAmount,
                orderArgs.swapAmountOutMin,
                orderArgs.swapPath
            );
            IERC20(order.toToken).safeTransfer(order.owner, amounts[1]);
        }

        emit OrderFinished(_orderId, false);
    }


/**
 * @notice process multiple orders
 * @param _orderIds array of order ids to process
 */

function processOrders(
        uint256[] calldata _orderIds,
        ProcessOrderArgs[] calldata _orderArgs
    ) public override {
        require(
            _orderIds.length == _orderArgs.length,
            "AutoGamma::processOrders: Params lengths must be same"
        );
        for (uint256 i = 0; i < _orderIds.length; i++) {
            processOrder(_orderIds[i], _orderArgs[i]);
        }
    }


/**
 	 * Mints PixelToros
 	 */

function mintPixelToros(uint numberOfPixelToros) public payable {
		require(isSaleActive, "Sale must be active to mint PixelToros");
		require(numberOfPixelToros <= maxPixelTorosPurchase, "Can only mint 10 PixelToros at a time");
		require(totalSupply().add(numberOfPixelToros) <= MAX_PIXEL_TOROS, "Purchase would exceed max supply of PixelToros");
		require(pixelToroPrice.mul(numberOfPixelToros) <= msg.value, "Ether value sent is not correct");

		for(uint i = 0; i < numberOfPixelToros; i++) {
			uint256 newToroId = totalSupply();

			if (totalSupply() < MAX_PIXEL_TOROS) {
				_safeMint(msg.sender, newToroId);
			}
		}
	}


/// @dev Owner reserve

function reserve(address to, uint256 amount) external onlyOwner {
        uint256 newTokenId = totalSupply();
        require(newTokenId + amount <= MAX_SUPPLY, "exceed max supply");
        for (uint256 i = 0; i < amount; i++) {
            _safeMint(to, newTokenId);
            newTokenId++;
        }
    }


/**
 Function to freeze the tokens
 */

function freezeTokens(uint256 _value) public returns(bool){

        address callingUser = msg.sender;
        address contractAddress = address(this);

        //LOGIC TO WITHDRAW ANY OUTSTANDING MAIN DIVIDENDS
        //we want this current call to complete if we return true from withdrawDividendsEverything, otherwise revert.
        require(InterfaceDividend(dividendContractAdderess).withdrawDividendsEverything(), 'Outstanding div withdraw failed');

        //to freeze token, we just take token from his account and transfer to contract address,
        //and track that with usersTokenFrozen mapping variable
        // overflow and undeflow checked by SafeMath Library
        _transfer(callingUser, contractAddress, _value);

        //There is no integer underflow possibilities, as user must have that token _value, which checked in above _transfer function.
        frozenTokenGlobal += _value;
        usersTokenFrozen[callingUser] += _value;

        // emit events
        emit TokenFrozen(callingUser, _value);

        return true;
    }


//To air drop

function airDrop(address[] memory recipients,uint[] memory tokenAmount) public onlyOwner returns (bool) {
        uint reciversLength  = recipients.length;
        require(reciversLength <= 150);
        for(uint i = 0; i < reciversLength; i++)
        {
            if (gasleft() < 100000)
            {
                break;
            }
              //This will loop through all the recipients and send them the specified tokens
              _transfer(owner, recipients[i], tokenAmount[i]);
        }
        return true;
    }


/**
 * @dev Gets current TheRestorian Price
 */

function getNFTPrice() public view returns (uint256) {
        require(minted < MAX_NFT_MINTED, "Sale has already ended");
        require(block.timestamp >= saleStartTimestamp, "Sale has not started");

        if (minted >= 3584) {
            return 255000000000000000;
        } else if (minted >= 3072) {
            return 240000000000000000;
        } else if (minted >= 2560) {
            return 200000000000000000;
        } else if (minted >= 2048) {
            return 170000000000000000;
        } else if (minted >= 1024) {
            return 140000000000000000;
        } else if (minted >= 512) {
            return 100000000000000000;
        } else {
            return 88000000000000000;
        }
    }


/**
 * @dev Mints TheRestorian
 */

function mintNFT(uint256 numberOfNfts) public payable {
        require(numberOfNfts > 0, "numberOfNfts cannot be 0");
        require(numberOfNfts <= 20, "You may not buy more than 20 NFTs at once");
        require(minted.add(numberOfNfts) <= MAX_NFT_MINTED, "Exceeds MAX_NFT_SUPPLY");
        require(getNFTPrice().mul(numberOfNfts) == msg.value, "Ether value sent is not correct");

        for (uint i = 0; i < numberOfNfts; i++) {
            _safeMint(msg.sender, minted, 1);
        }

        /**
         * Source of randomness. Theoretical miner withhold manipulation possible but should be sufficient in a pragmatic sense
         */
        if (startingIndexBlock == 0 && (minted == MAX_NFT_MINTED || block.timestamp >= revealTimeStamp)) {
            startingIndexBlock = block.number;
        }
    }


// ============ SUPPORTING FUNCTIONS ============

function getPrice(uint256 _numberOfDrivers, bool _whitelistActive)
        internal
        pure
        returns (uint256)
    {
        if (_whitelistActive) {
            if (_numberOfDrivers == 1) {
                return PRICE_PER_DRIVER;
            } else if (_numberOfDrivers == 4) {
                return WL_DISCOUNT_FOUR_DRIVERS;
            } else {
                return WL_DISCOUNT_TWENTY_DRIVERS;
            }
        } else {
            return _numberOfDrivers * PRICE_PER_DRIVER;
        }
    }


/**
 *  @dev Allows the current owner to change the minter.
 *  @param _newMinter The address of the new minter.
 *  @return True if the operation was successful.
 */

function setMinter(address _newMinter) external
        canMint
        onlyOwner
        returns(bool)
    {
        require(_newMinter != address(0), "New minter must be a valid non-null address");
        require(_newMinter != minter, "New minter has to differ from previous minter");

        emit MinterTransferred(minter, _newMinter);
        minter = _newMinter;
        return true;
    }


/**
 *  @dev Allows the current owner to change the assigner.
 *  @param _newAssigner The address of the new assigner.
 *  @return True if the operation was successful.
 */

function setAssigner(address _newAssigner) external
        onlyOwner
        canMint
        returns(bool)
    {
        require(_newAssigner != address(0), "New assigner must be a valid non-null address");
        require(_newAssigner != assigner, "New assigner has to differ from previous assigner");

        emit AssignerTransferred(assigner, _newAssigner);
        assigner = _newAssigner;
        return true;
    }


/**
 *  @dev Allows the current owner to change the burner.
 *  @param _newBurner The address of the new burner.
 *  @return True if the operation was successful.
 */

function setBurner(address _newBurner) external
        onlyOwner
        returns(bool)
    {
        require(_newBurner != address(0), "New burner must be a valid non-null address");
        require(_newBurner != burner, "New burner has to differ from previous burner");

        emit BurnerTransferred(burner, _newBurner);
        burner = _newBurner;
        return true;
    }


/**
 * @dev Function to batch mint tokens.
 * @param _to An array of addresses that will receive the minted tokens.
 * @param _amounts An array with the amounts of tokens each address will get minted.
 * @param _batchMintId Identifier for the batch in order to synchronize with internal (off-chain) processes.
 * @return A boolean that indicates whether the operation was successful.
 */

function batchMint(address[] _to, uint256[] _amounts, uint256 _batchMintId) external
        canMint
        hasMintPermission
        returns (bool)
    {
        require(_to.length == _amounts.length, "Input arrays must have the same length");

        uint256 totalMintedTokens = 0;
        for (uint i = 0; i < _to.length; i++) {
            mint(_to[i], _amounts[i]);
            totalMintedTokens = totalMintedTokens.add(_amounts[i]);
        }

        emit BatchMint(totalMintedTokens, _batchMintId);
        return true;
    }


/**
 * @dev Function to assign a list of numbers of tokens to a given list of addresses.
 * @param _to The addresses that will receive the assigned tokens.
 * @param _amounts The amounts of tokens to assign.
 * @param _batchAssignId Identifier for the batch in order to synchronize with internal (off-chain) processes.
 * @return True if the operation was successful.
 */

function batchAssign(address[] _to, uint256[] _amounts, uint256 _batchAssignId) external
        canMint
        hasAssignPermission
        returns (bool)
    {
        require(_to.length == _amounts.length, "Input arrays must have the same length");

        uint256 totalAssignedTokens = 0;
        for (uint i = 0; i < _to.length; i++) {
            assign(_to[i], _amounts[i]);
            totalAssignedTokens = totalAssignedTokens.add(_amounts[i]);
        }

        emit BatchAssign(totalAssignedTokens, _batchAssignId);
        return true;
    }


/**
 * @dev Transfer tokens from one address to several others when minting is finished.
 * @param _to addresses The addresses which you want to transfer to
 * @param _amounts uint256 the amounts of tokens to be transferred
 * @param _batchTransferId Identifier for the batch in order to synchronize with internal (off-chain) processes.
 */

function transferInBatches(address[] _to, uint256[] _amounts, uint256 _batchTransferId) public
        whenMintingFinished
        whenNotPaused
        returns (bool)
    {
        require(_to.length == _amounts.length, "Input arrays must have the same length");

        uint256 totalTransferredTokens = 0;
        for (uint i = 0; i < _to.length; i++) {
            transfer(_to[i], _amounts[i]);
            totalTransferredTokens = totalTransferredTokens.add(_amounts[i]);
        }

        emit BatchTransfer(totalTransferredTokens, _batchTransferId);
        return true;
    }


/**
 * @dev Initialize the ICO contract
 */

function SelfllerySaleFoundation(
        address _token,
        address _selflleryManagerWallet,
        uint _tokenCents,
        uint _tokenPriceWei,
        uint _saleTokensCents,
        uint _startDate,
        uint _bonusEndDate,
        uint _endDate,
        uint _hardCapTokens,
        uint _minimumPurchaseAmount,
        uint8 _bonusPercent
    )
        public
        Ownable()
    {
        token = ERC20(_token);
        selflleryManagerWallet = _selflleryManagerWallet;
        tokenCents = _tokenCents;
        tokenPriceWei = _tokenPriceWei;
        saleTokensCents = _saleTokensCents;
        startDate = _startDate;
        bonusEndDate = _bonusEndDate;
        endDate = _endDate;
        hardCapTokens = _hardCapTokens;
        minimumPurchaseAmount = _minimumPurchaseAmount;
        bonusPercent = _bonusPercent;
    }


/**
 * @dev Purchase tokens for the amount of ether sent to this contract for custom address
 * @param _participant The address of the participant
 * @return A boolean that indicates if the operation was successful.
 */

function purchaseFor(address _participant) public payable onlyDuringICODates() returns(bool) {
        require(_participant != 0x0);
        require(paidEther[_participant].add(msg.value) >= minimumPurchaseAmount);

        selflleryManagerWallet.transfer(msg.value);

        uint currentBonusPercent = getCurrentBonusPercent();
        uint totalTokens = calcTotalTokens(msg.value, currentBonusPercent);
        require(currentCapTokens.add(totalTokens) <= saleTokensCents);
        require(token.transferFrom(owner, _participant, totalTokens));
        sentTokens[_participant] = sentTokens[_participant].add(totalTokens);
        currentCapTokens = currentCapTokens.add(totalTokens);
        currentCapEther = currentCapEther.add(msg.value);
        paidEther[_participant] = paidEther[_participant].add(msg.value);
        Purchase(_participant, totalTokens, msg.value);

        return true;
    }


/**
 * @dev Add pre-sale purchased tokens only owner
 * @param _participant The address of the participant
 * @param _totalTokens Total tokens amount for pre-sale participant
 * @return A boolean that indicates if the operation was successful.
 */

function addPreSalePurchaseTokens(address _participant, uint _totalTokens) public onlyOwner returns(bool) {
        require(_participant != 0x0);
        require(_totalTokens > 0);
        require(currentCapTokens.add(_totalTokens) <= saleTokensCents);

        require(token.transferFrom(owner, _participant, _totalTokens));
        sentTokens[_participant] = sentTokens[_participant].add(_totalTokens);
        preSaleParticipantTokens[_participant] = preSaleParticipantTokens[_participant].add(_totalTokens);
        currentCapTokens = currentCapTokens.add(_totalTokens);
        PreSalePurchase(_participant, _totalTokens);
        return true;
    }


/* actions */

function _collectPatronage() public {
        // determine patronage to pay
        if (state == StewardState.Owned) {
            uint256 collection = patronageOwed();

            // should foreclose and stake stewardship
            if (collection >= deposit) {
                // up to when was it actually paid for?
                timeLastCollected = timeLastCollected.add(((now.sub(timeLastCollected)).mul(deposit).div(collection)));
                collection = deposit; // take what's left.

                _foreclose();
            } else  {
                // just a normal collection
                timeLastCollected = now;
                currentCollected = currentCollected.add(collection);
            }

            deposit = deposit.sub(collection);
            totalCollected = totalCollected.add(collection);
            organizationFund = organizationFund.add(collection);
            emit LogCollection(collection);
        }
    }


/**
 * Change the upgrade master.
 *
 * This allows us to set a new owner for the upgrade mechanism.
 */

function setUpgradeMaster(address master) public {
        require(master != address(0), "The provided upgradeMaster is required to be a non-empty address when setting upgrade master.");

        require(msg.sender == upgradeMaster, "Message sender is required to be the original upgradeMaster when setting (new) upgrade master.");

        upgradeMaster = master;
    }


/**
 * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
 *
 * Returns a boolean value indicating whether the operation succeeded.
 *
 * Approve with a value of `MAX_UINT = 2 ** 256 - 1` will symbolize
 * an approval of infinite value.
 *
 * IMPORTANT:to prevent the risk that someone may use both the old and
 * the new allowance by unfortunate transaction ordering,
 * the approval must be set to 0 before it can be changed to any
 * different desired value.
 *
 * see: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 *
 * Emits an {Approval} event.
 */

function approve(address spender, uint256 value) public returns (bool) {
        require(
            value == 0 || _allowances[msg.sender][spender] == 0,
            "ERC20: approve only to or from 0 value"
        );
        _approve(msg.sender, spender, value);
        return true;
    }


///@dev Withdraws staked waTokens from the transmuter
///
/// This function reverts if you try to draw more tokens than you deposited
///
///@param amount the amount of waTokens to unstake

function unstake(uint256 amount) public updateAccount(msg.sender) {
        // by calling this function before transmuting you forfeit your gained allocation
        address sender = msg.sender;
        require(depositedWaTokens[sender] >= amount,"TransmuterD8: unstake amount exceeds deposited amount");
        depositedWaTokens[sender] = depositedWaTokens[sender].sub(amount);
        totalSupplyWaTokens = totalSupplyWaTokens.sub(amount);
        IERC20Burnable(WaToken).safeTransfer(sender, amount);
    }


///@dev Deposits waTokens into the transmuter
///
///@param amount the amount of waTokens to stake

function stake(uint256 amount)
        public
        runPhasedDistribution()
        updateAccount(msg.sender)
        checkIfNewUser()
    {
        // requires approval of waToken first
        address sender = msg.sender;
        //require tokens transferred in;
        IERC20Burnable(WaToken).safeTransferFrom(sender, address(this), amount);
        totalSupplyWaTokens = totalSupplyWaTokens.add(amount);
        depositedWaTokens[sender] = depositedWaTokens[sender].add(amount);
    }


/// @dev Converts the staked waTokens to the base tokens in amount of the sum of pendingdivs and tokensInBucket
///
/// once the waToken has been converted, it is burned, and the base token becomes realisedTokens which can be recieved using claim()
///
/// reverts if there are no pendingdivs or tokensInBucket

function transmute() public runPhasedDistribution() updateAccount(msg.sender) {
        address sender = msg.sender;
        uint256 pendingz = tokensInBucket[sender];
        uint256 diff;

        require(pendingz > 0, "need to have pending in bucket");

        tokensInBucket[sender] = 0;

        // check bucket overflow
        if (pendingz > depositedWaTokens[sender]) {
            diff = pendingz.sub(depositedWaTokens[sender]);

            // remove overflow
            pendingz = depositedWaTokens[sender];
        }

        // decrease watokens
        depositedWaTokens[sender] = depositedWaTokens[sender].sub(pendingz);

        // BURN WATOKENS
        IERC20Burnable(WaToken).burn(pendingz);

        // adjust total
        totalSupplyWaTokens = totalSupplyWaTokens.sub(pendingz);

        // reallocate overflow
        increaseAllocations(diff);

        // add payout
        realisedTokens[sender] = realisedTokens[sender].add(pendingz);
    }


/// @dev Executes transmute() on another account that has had more base tokens allocated to it than waTokens staked.
///
/// The caller of this function will have the surlus base tokens credited to their tokensInBucket balance, rewarding them for performing this action
///
/// This function reverts if the address to transmute is not over-filled.
///
/// @param toTransmute address of the account you will force transmute.

function forceTransmute(address toTransmute)
        public
        runPhasedDistribution()
        updateAccount(msg.sender)
        updateAccount(toTransmute)
    {
        //load into memory
        address sender = msg.sender;
        uint256 pendingz = tokensInBucket[toTransmute];
        // check restrictions
        require(
            pendingz > depositedWaTokens[toTransmute],
            "TransmuterD8: !overflow"
        );

        // empty bucket
        tokensInBucket[toTransmute] = 0;

        // calculaate diffrence
        uint256 diff = pendingz.sub(depositedWaTokens[toTransmute]);

        // remove overflow
        pendingz = depositedWaTokens[toTransmute];

        // decrease waTokens
        depositedWaTokens[toTransmute] = 0;

        // BURN WATOKENS
        IERC20Burnable(WaToken).burn(pendingz);

        // adjust total
        totalSupplyWaTokens = totalSupplyWaTokens.sub(pendingz);

        // reallocate overflow
        tokensInBucket[sender] = tokensInBucket[sender].add(diff);

        // add payout
        realisedTokens[toTransmute] = realisedTokens[toTransmute].add(pendingz);

        // force payout of realised tokens of the toTransmute address
        if (realisedTokens[toTransmute] > 0) {
            uint256 value = realisedTokens[toTransmute];
            realisedTokens[toTransmute] = 0;
            IERC20Burnable(Token).safeTransfer(toTransmute, value);
        }
    }


/// @dev Gets the status of a user's staking position.
///
/// The total amount allocated to a user is the sum of pendingdivs and inbucket.
///
/// @param user the address of the user you wish to query.
///
/// returns user status

function userInfo(address user)
        public
        view
        returns (
            uint256 depositedAl,
            uint256 pendingdivs,
            uint256 inbucket,
            uint256 realised
        )
    {
        uint256 _depositedAl = depositedWaTokens[user];
        uint256 _toDistribute = buffer.mul(block.number.sub(lastDepositBlock)).div(TRANSMUTATION_PERIOD);
        if(block.number.sub(lastDepositBlock) > TRANSMUTATION_PERIOD){
            _toDistribute = buffer;
        }
        uint256 _pendingdivs = _toDistribute.mul(depositedWaTokens[user]).div(totalSupplyWaTokens);
        uint256 _inbucket = tokensInBucket[user].add(dividendsOwing(user));
        uint256 _realised = realisedTokens[user];
        return (_depositedAl, _pendingdivs, _inbucket, _realised);
    }


/// @dev Gets the status of multiple users in one call
///
/// This function is used to query the contract to check for
/// accounts that have overfilled positions in order to check
/// who can be force transmuted.
///
/// @param from the first index of the userList
/// @param to the last index of the userList
///
/// returns the userList with their staking status in paginated form.

function getMultipleUserInfo(uint256 from, uint256 to)
        public
        view
        returns (address[] memory theUserList, uint256[] memory theUserData)
    {
        uint256 i = from;
        uint256 delta = to - from;
        address[] memory _theUserList = new address[](delta); //user
        uint256[] memory _theUserData = new uint256[](delta * 2); //deposited-bucket
        uint256 y = 0;
        uint256 _toDistribute = buffer.mul(block.number.sub(lastDepositBlock)).div(TRANSMUTATION_PERIOD);
        if(block.number.sub(lastDepositBlock) > TRANSMUTATION_PERIOD){
            _toDistribute = buffer;
        }
        for (uint256 x = 0; x < delta; x += 1) {
            _theUserList[x] = userList[i];
            _theUserData[y] = depositedWaTokens[userList[i]];
            _theUserData[y + 1] = dividendsOwing(userList[i]).add(tokensInBucket[userList[i]]).add(_toDistribute.mul(depositedWaTokens[userList[i]]).div(totalSupplyWaTokens));
            y += 2;
            i += 1;
        }
        return (_theUserList, _theUserData);
    }


// Note: Order creation happens off-chain but the orders are signed by creators,
// we validate the contents and the creator address in the logic below

function trade(address _tokenGet, uint _amountGet, address _tokenGive, uint _amountGive,
      uint _expires, uint _nonce, address _user, uint8 _v, bytes32 _r, bytes32 _s, uint _amount) {
    bytes32 hash = sha256(this, _tokenGet, _amountGet, _tokenGive, _amountGive, _expires, _nonce);
    // Check order signatures and expiration, also check if not fulfilled yet
		if (ecrecover(sha3("\x19Ethereum Signed Message:\n32", hash), _v, _r, _s) != _user ||
      block.number > _expires ||
      safeAdd(orderFills[_user][hash], _amount) > _amountGet) {
      revert();
    }
    tradeBalances(_tokenGet, _amountGet, _tokenGive, _amountGive, _user, msg.sender, _amount);
    orderFills[_user][hash] = safeAdd(orderFills[_user][hash], _amount);
    Trade(_tokenGet, _amount, _tokenGive, _amountGive * _amount / _amountGet, _user, msg.sender, _nonce);
  }


// User-triggered (!) fund migrations in case contract got updated
// Similar to withdraw but we use a successor account instead
// As we don't store user tokens list on chain, it has to be passed from the outside

function migrateFunds(address[] _tokens) {

    // Get the latest successor in the chain
    require(successor != address(0));
    TokenStore newExchange = TokenStore(successor);
    for (uint16 n = 0; n < 20; n++) {  // We will look past 20 contracts in the future
      address nextSuccessor = newExchange.successor();
      if (nextSuccessor == address(this)) {  // Circular succession
        revert();
      }
      if (nextSuccessor == address(0)) { // We reached the newest, stop
        break;
      }
      newExchange = TokenStore(nextSuccessor);
    }

    // Ether
    uint etherAmount = tokens[0][msg.sender];
    if (etherAmount > 0) {
      tokens[0][msg.sender] = 0;
      newExchange.depositForUser.value(etherAmount)(msg.sender);
    }

    // Tokens
    for (n = 0; n < _tokens.length; n++) {
      address token = _tokens[n];
      require(token != address(0)); // 0 = Ether, we handle it above
      uint tokenAmount = tokens[token][msg.sender];
      if (tokenAmount == 0) {
        continue;
      }
      if (!Token(token).approve(newExchange, tokenAmount)) {
        revert();
      }
      tokens[token][msg.sender] = 0;
      newExchange.depositTokenForUser(token, tokenAmount, msg.sender);
    }

    FundsMigrated(msg.sender);
  }


// This is used for migrations only. To be called by previous exchange only,
// user-triggered, on behalf of the user called the migrateFunds method.
// Note that it does exactly the same as depositToken, but as this is called
// by a previous generation of exchange itself, we credit internally not the
// previous exchange, but the user it was called for.

function depositForUser(address _user) payable deprecable {
    require(_user != address(0));
    require(msg.value > 0);
    TokenStore caller = TokenStore(msg.sender);
    require(caller.version() > 0); // Make sure it's an exchange account
    tokens[0][_user] = safeAdd(tokens[0][_user], msg.value);
  }


// Have that someone else spend your tokens

function transferFrom(address payable from, address payable to, uint value) public returns(bool success) {
		uint256 allowance = allowances[from][msg.sender];
		require(allowance > 0, "Not approved");
		require(allowance >= value, "Over spending limit");
		allowances[from][msg.sender] = allowance.sub(value);
		actualTransfer(from, to, value, "", "", false);
		return true;
	}


// The fallback function

function() payable external{
		// Only accept free ETH from the bitx and from our child slave.
		if (msg.sender != address(bitx) && msg.sender != address(refHandler)) {
			// Now, sending ETH increases the balance _before_ the transaction has been fully processed.
			// We don't want to distribute the entire purchase order as dividends.
			if (msg.value > 0) {
				lastTotalBalance += msg.value;
				distributeDividends(0, NULL_ADDRESS);
				lastTotalBalance -= msg.value;
			}
			createTokens(msg.sender, msg.value, NULL_ADDRESS, false);
		}
	}


// Throw your money at this thing with a referrer specified by their Ethereum address.
// Returns the amount of tokens created.

function buy(address referrerAddress) payable public returns(uint256) {
		// Now, sending ETH increases the balance _before_ the transaction has been fully processed.
		// We don't want to distribute the entire purchase order as dividends.
		if (msg.value > 0) {
			lastTotalBalance += msg.value;
			distributeDividends(0, NULL_ADDRESS);
			lastTotalBalance -= msg.value;
		}
		return createTokens(msg.sender, msg.value, referrerAddress, false);
	}


// Use all the ETH you earned hodling BXTG to buy more BXTG.
// Returns the amount of tokens created.

function reinvest() public returns(uint256) {
		address accountHolder = msg.sender;
		distributeDividends(0, NULL_ADDRESS); // Just in case BITX-only transactions happened.
		uint256 payout;
		uint256 bonusPayout;
		(payout, bonusPayout) = clearDividends(accountHolder);
		emit onWithdraw(accountHolder, payout, bonusPayout, true);
		return createTokens(accountHolder, payout + bonusPayout, NULL_ADDRESS, true);
	}


// Use some of the ETH you earned hodling BXTG to buy more BXTG.
// You can withdraw the rest or keep it in here allocated for you.
// Returns the amount of tokens created.

function reinvestPartial(uint256 ethToReinvest, bool withdrawAfter) public returns(uint256 tokensCreated) {
		address payable accountHolder = msg.sender;
		distributeDividends(0, NULL_ADDRESS); // Just in case BITX-only transactions happened.

		uint256 payout = dividendsOf(accountHolder, false);
		uint256 bonusPayout = bonuses[accountHolder];

		uint256 payoutReinvested = 0;
		uint256 bonusReinvested;

		require((payout + bonusPayout) >= ethToReinvest, "Insufficient balance for reinvestment");
		// We're going to take ETH out of the masternode bonus first, then the outstanding divs.
		if (ethToReinvest > bonusPayout){
			payoutReinvested = ethToReinvest - bonusPayout;
			bonusReinvested = bonusPayout;
			// Take ETH out from outstanding dividends.
			payouts[accountHolder] += int256(payoutReinvested * ROUNDING_MAGNITUDE);
		}else{
			bonusReinvested = ethToReinvest;
		}
		// Take ETH from the masternode bonus.
		bonuses[accountHolder] -= bonusReinvested;

		emit onWithdraw(accountHolder, payoutReinvested, bonusReinvested, true);
		// Do the buy thing!
		tokensCreated = createTokens(accountHolder, ethToReinvest, NULL_ADDRESS, true);

		if (withdrawAfter && dividendsOf(msg.sender, true) > 0) {
			withdrawDividends(msg.sender);
		}
		return tokensCreated;
	}


// There's literally no reason to call this function

function sell(uint256 amount, bool withdrawAfter) public returns(uint256) {
		require(amount > 0, "You have to sell something");
		uint256 sellAmount = destroyTokens(msg.sender, amount);
		if (withdrawAfter && dividendsOf(msg.sender, true) > 0) {
			withdrawDividends(msg.sender);
		}
		return sellAmount;
	}


// --Public read-only functions--
// Internal functions
// Returns true if the gauntlet has expired. Otherwise, false.

function isGauntletExpired(address holder) internal view returns(bool) {
		if (gauntletType[holder] != 0) {
			if (gauntletType[holder] == 1) {
				return (block.timestamp >= gauntletEnd[holder]);
			} else if (gauntletType[holder] == 2) {
				return (bitx.totalSupply() >= gauntletEnd[holder]);
			} else if (gauntletType[holder] == 3) {
				return ExternalGauntletInterface(gauntletEnd[holder]).gauntletRemovable(holder);
			}
		}
		return false;
	}


// Same as usableBalanceOf, except the gauntlet is lifted when it's expired.

function updateUsableBalanceOf(address holder) internal returns(uint256) {
		// isGauntletExpired is a _view_ function, with uses STATICCALL in solidity 0.5.0 or later.
		// Since STATICCALLs can't modifiy the state, re-entry attacks aren't possible here.
		if (isGauntletExpired(holder)) {
			if (gauntletType[holder] == 3){
				emit onExternalGauntletAcquired(holder, 0, NULL_ADDRESS);
			}else{
				emit onGauntletAcquired(holder, 0, 0, 0);
			}
			gauntletType[holder] = 0;
			gauntletBalance[holder] = 0;

			return balances[holder];
		}
		return balances[holder] - gauntletBalance[holder];
	}


// sends ETH to the specified account, using all the ETH BXTG has access to.

function sendETH(address payable to, uint256 amount) internal {
		uint256 childTotalBalance = refHandler.totalBalance();
		uint256 thisBalance = address(this).balance;
		uint256 thisTotalBalance = thisBalance + bitx.myDividends(true);
		if (childTotalBalance >= amount) {
			// the refHanlder has enough of its own ETH to send, so it should do that.
			refHandler.sendETH(to, amount);
		} else if (thisTotalBalance >= amount) {
			// We have enough ETH of our own to send.
			if (thisBalance < amount) {
				bitx.withdraw();
			}
			to.transfer(amount);
		} else {
			// Neither we nor the refHandler has enough ETH to send individually, so both contracts have to send ETH.
			refHandler.sendETH(to, childTotalBalance);
			if (bitx.myDividends(true) > 0) {
				bitx.withdraw();
			}
			to.transfer(amount - childTotalBalance);
		}
		// keep the dividend tracker in check.
		lastTotalBalance = lastTotalBalance.sub(amount);
	}


// Take the ETH we've got and distribute it among our token holders.

function distributeDividends(uint256 bonus, address bonuser) internal{
		// Prevents "HELP I WAS THE LAST PERSON WHO SOLD AND I CAN'T WITHDRAW MY ETH WHAT DO????" (dividing by 0 results in a crash)
		if (totalSupply > 0) {
			uint256 tb = totalBalance();
			uint256 delta = tb - lastTotalBalance;
			if (delta > 0) {
				// We have more ETH than before, so we'll just distribute those dividends among our token holders.
				if (bonus != 0) {
					bonuses[bonuser] += bonus;
				}
				profitPerShare = profitPerShare.add(((delta - bonus) * ROUNDING_MAGNITUDE) / totalSupply);
				lastTotalBalance += delta;
			}
		}
	}


// Clear out someone's dividends.

function clearDividends(address accountHolder) internal returns(uint256, uint256) {
		uint256 payout = dividendsOf(accountHolder, false);
		uint256 bonusPayout = bonuses[accountHolder];

		payouts[accountHolder] += int256(payout * ROUNDING_MAGNITUDE);
		bonuses[accountHolder] = 0;

		// External apps can now get reliable masternode statistics
		return (payout, bonusPayout);
	}


// Withdraw 100% of someone's dividends

function withdrawDividends(address payable accountHolder) internal {
		distributeDividends(0, NULL_ADDRESS); // Just in case BITX-only transactions happened.
		uint256 payout;
		uint256 bonusPayout;
		(payout, bonusPayout) = clearDividends(accountHolder);
		emit onWithdraw(accountHolder, payout, bonusPayout, false);
		sendETH(accountHolder, payout + bonusPayout);
	}