// @notice set the step of the campaign
// @param _step {Step}

function setStep(Step _step) public onlyOwner() {
        currentStep = _step;

        if (currentStep == Step.FundingPreSale) {  // for presale

            minInvestETH = 1 ether/5;
        }else if (currentStep == Step.FundingPublicSale) { // for public sale
            minInvestETH = 1 ether/10;
        }
    }


// @notice Due to changing average of block time
// this function will allow on adjusting duration of campaign closer to the end

function adjustDuration(uint _block) external onlyOwner() {

        require(_block < 389376);  // 4.16*60*24*65 days = 389376
        require(_block > block.number.sub(startBlock)); // ensure that endBlock is not set in the past
        endBlock = startBlock.add(_block);
    }


// @notice determine if purchase is valid and return proper number of tokens
// @return tokensToSend {uint} proper number of tokens based on the timline

function determinePurchase() internal view  returns (uint) {

        require(msg.value >= minInvestETH);                        // ensure that min contributions amount is met
        uint tokenAmount = msg.value.mul(1e18) / tokenPriceWei;    // calculate amount of tokens

        uint tokensToSend;

        if (currentStep == Step.FundingPreSale)
            tokensToSend = calculateNoOfTokensToSend(tokenAmount);
        else
            tokensToSend = tokenAmount;

        return tokensToSend;
    }


// @notice This function will return number of tokens based on time intervals in the campaign
// @param _tokenAmount {uint} amount of tokens to allocate for the contribution

function calculateNoOfTokensToSend(uint _tokenAmount) internal view  returns (uint) {

        if (block.number <= startBlock + (numOfBlocksInMinute * 60 * 24 * 14) / 100)        // less equal then/equal 14 days
            return  _tokenAmount + (_tokenAmount * 40) / 100;  // 40% bonus
        else if (block.number <= startBlock + (numOfBlocksInMinute * 60 * 24 * 28) / 100)   // less equal  28 days
            return  _tokenAmount + (_tokenAmount * 30) / 100; // 30% bonus
        else
            return  _tokenAmount + (_tokenAmount * 20) / 100;   // remainder of the campaign 20% bonus

    }


// @notice called to send tokens to contributors after ICO and lockup period.
// @param _backer {address} address of beneficiary
// @return true if successful

function claimTokensForUser(address _backer) internal returns(bool) {

        require(currentStep == Step.Claiming);

        Backer storage backer = backers[_backer];

        require(!backer.refunded);      // if refunded, don't allow for another refund
        require(!backer.claimed);       // if tokens claimed, don't allow refunding
        require(backer.tokensToSend != 0);   // only continue if there are any tokens to send

        claimCount++;
        claimed[_backer] = backer.tokensToSend;  // save claimed tokens
        backer.claimed = true;
        totalClaimed += backer.tokensToSend;

        if (!token.transfer(_backer, backer.tokensToSend))
            revert(); // send claimed tokens to contributor account

        TokensClaimed(_backer, backer.tokensToSend);
    }


// @notice it will allow contributors to get refund in case campaign failed

function refund() external stopInEmergency returns (bool) {

        require(currentStep == Step.Refunding);

        require(this.balance > 0);  // contract will hold 0 ether at the end of campaign.
                                    // contract needs to be funded through fundContract()

        Backer storage backer = backers[msg.sender];

        require(backer.weiReceived > 0);  // esnure that user has sent contribution
        require(!backer.refunded);         // ensure that user hasn't been refunded yet
        require(!backer.claimed);       // if tokens claimed, don't allow refunding

        backer.refunded = true;  // save refund status to true

        refundCount++;
        totalRefunded = totalRefunded.add(backer.weiReceived);
        msg.sender.transfer(backer.weiReceived);  // send back the contribution
        RefundETH(msg.sender, backer.weiReceived);
        return true;
    }


/// Create a new Lotto

function LottoCount() public
    {
        worldOwner = msg.sender;

        ticketPrice = 0.0101 * 10**18;
        maxTickets = 10;

		_direction = 0;
        lottoIndex = 1;
        lastTicketTime = 0;

        numtickets = 0;
        totalBounty = 0;
    }


// Deposits ore to be burned

function burnOre(uint256 _amount) external nonReentrant {
    require(_openForBurning, "Not Open For Burning");
    require(_amount > 0, "Invalid Ore Amount");
    require(ore.balanceOf(msg.sender) >= _amount, "Insufficient Ore");

    ore.burn(msg.sender, _amount);

    _burntOreByAddress[msg.sender][_currentLaunch] += _amount;
    _totalBurntOreByLaunch[_currentLaunch] += _amount;
  }


// Calculates and returns the total amount of claimable funds for the specified account and period

function totalClaimableByAccount(
    address _account,
    uint256 _fromLaunch,
    uint256 _toLaunch
  ) public view returns (
    uint256,
    uint256[] memory
  ) {
    require(_fromLaunch > 0 && _toLaunch <= _currentLaunch && _fromLaunch <= _toLaunch, "Invalid Launch Period");

    // Calculate the total claimable amount along with the detail for each launch in the specified period range
    uint256 totalClaimable = 0;
    uint256 periodSize = _toLaunch - _fromLaunch + 1;
    uint256[] memory maxPerLaunch = new uint256[](periodSize);
    for (uint256 i = _fromLaunch; i <= _toLaunch ; i++) {
      uint256 maxAmount = 0;

      if (_totalBurntOreByLaunch[i] > 0 && _totalFundsByLaunch[i] > 0) {
        maxAmount = (_burntOreByAddress[_account][i] * _totalFundsByLaunch[i]) / _totalBurntOreByLaunch[i];
        uint256 claimable = maxAmount - _claimedByAddress[_account][i];

        if (claimable > 0) {
          totalClaimable += claimable;
        }
      }

      uint256 index = i - _fromLaunch;
      maxPerLaunch[index] = maxAmount;
    }

    return (totalClaimable, maxPerLaunch);
  }


// Can be called by collectors for claiming funds from the ore burning mechanism

function claim(uint256 _fromLaunch, uint256 _toLaunch) external nonReentrant {
    uint256 totalClaimable;
    uint256[] memory maxPerLaunch;
    (totalClaimable, maxPerLaunch) = totalClaimableByAccount(msg.sender, _fromLaunch, _toLaunch);

    // Check the claimable amount and update the total claimed so far for the account
    require(totalClaimable > 0, "Insufficient Claimable Funds");
    for (uint256 i = _fromLaunch; i <= _toLaunch; i++) {
      uint256 index = i - _fromLaunch;
      if (_claimedByAddress[msg.sender][i] < maxPerLaunch[index]) {
        _claimedByAddress[msg.sender][i] = maxPerLaunch[index];
      }
    }

    payable(msg.sender).transfer(totalClaimable);
  }


// Handles funds received for the pool

receive() external payable {
    // Make sure enough funds are sent, and the source is whitelisted
    require(msg.value > 0, "Insufficient Funds");
    require(fundAddresses[msg.sender], "Invalid Fund Address");

    // Update the total received funds for the currently open launch
    _totalFundsByLaunch[_currentLaunch] += msg.value;
  }


/* function registerNameXID(string _nameString, uint256 _affCode, bool _all)
 isHuman()
 public
 payable
 {
 bytes32 _name = _nameString.nameFilter();
 address _addr = msg.sender;
 uint256 _paid = msg.value;
 (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXIDFromDapp.value(_paid)(_addr, _name, _affCode, _all);

 uint256 _pID = pIDxAddr_[_addr];

 // fire event
 emit F3Devents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
 } */

function registerNameXaddr(string _nameString, address _affCode, bool _all)
          isHuman()
          public
          payable
      {
          bytes32 _name = _nameString.nameFilter();
          address _addr = msg.sender;
          uint256 _paid = msg.value;
          (bool _isNewPlayer, uint256 _affID) = PlayerBook.registerNameXaddrFromDapp.value(msg.value)(msg.sender, _name, _affCode, _all);

          uint256 _pID = pIDxAddr_[_addr];

          // fire event
          emit F3Devents.onNewName(_pID, _addr, _name, _isNewPlayer, _affID, plyr_[_affID].addr, plyr_[_affID].name, _paid, now);
      }


// An internal convenience function that checks to see if we are currently
// in the Window defined by the WindowParameters struct passed as an
// argument.

function _isInWindow(WindowParameters memory localParams) internal view returns (bool) {
        // We are never "in a window" if the contract is paused
        if (block.number < localParams.pauseEndedBlock) {
            return false;
        }

        // If we are before the first window of this type, we are obviously NOT
        // in this window!
        if (block.number < localParams.firstWindowStartBlock) {
            return false;
        }

        // Use modulus to figure out how far we are past the beginning of the
        // most recent window
        // of this type
        uint256 windowOffset = (block.number - localParams.firstWindowStartBlock) % localParams.sessionDuration;

        // If we are in the window, we will be within duration of the start of
        // the most recent window
        return windowOffset < localParams.windowDuration;
    }


/// @notice A getter function for determining whether we are currently in a
///  fightWindow for a given tournament
/// @param _basicTournamentAddress  The address of the tournament in
///  question
/// @return  Whether or not we are in a fightWindow for this tournament

function _isInFightWindowForTournament(address _basicTournamentAddress) internal view returns (bool){
        uint256 tournamentStartBlock;
        uint256 pauseEndedBlock;
        uint256 admissionDuration;
        uint256 duelTimeoutDuration;
        uint256 ascensionWindowDuration;
        uint256 fightWindowDuration;
        uint256 cullingWindowDuration;
        (
            tournamentStartBlock,
            pauseEndedBlock,
            admissionDuration,
            ,
            duelTimeoutDuration,
            ,
            ascensionWindowDuration,
            ,
            fightWindowDuration,
            ,
            ,
            ,
            cullingWindowDuration
        ) = IBasicTournamentTimeParams(_basicTournamentAddress).getTimeParameters();
        uint256 firstSessionStartBlock = uint256(tournamentStartBlock) + uint256(admissionDuration);
        uint256 sessionDuration = uint256(ascensionWindowDuration) + uint256(fightWindowDuration) + uint256(duelTimeoutDuration) + uint256(cullingWindowDuration);

        return _isInWindow(WindowParameters({
            firstWindowStartBlock: uint48(uint256(firstSessionStartBlock) + uint256(ascensionWindowDuration)),
            pauseEndedBlock: uint48(pauseEndedBlock),
            sessionDuration: uint32(sessionDuration),
            windowDuration: uint32(fightWindowDuration)
        }));
    }


/// @notice A seller/maker calls this function to update the pricePerPower
///  of an order that they already have stored in this contract. This saves
///  some gas for the seller by allowing them to just update the
///  pricePerPower parameter of their order.
/// @param _wizardId  The tokenId for the seller's wizard. This is the
///  wizard that will relinquish its power if an order is fulfilled.
/// @param _newPricePerPower  An order creator specifies how much wei they
///  are willing to sell their wizard's power for. Note that this price is
///  per-power, so the buyer will actually have to pay this price
///  multiplied by the amount of power that the wizard has.
/// @param _basicTournamentAddress  The address of the tournament that this
///  order pertains to. Note that each Wizard has an independent power
///  value for each tournament, and call sell each one separately, so
///  orders can exist simultaneously for the same wizard for different
///  tournaments.

function updateSellOrder(uint256 _wizardId, uint256 _newPricePerPower, address _basicTournamentAddress) external whenNotPaused nonReentrant {
        require(WizardGuild(wizardGuildAddress).ownerOf(_wizardId) == msg.sender, 'only the owner of the wizard can update a sell order');
        require(WizardGuild(wizardGuildAddress).isApprovedOrOwner(address(this), _wizardId), 'you must call the approve() function on WizardGuild before you can update a sell order');
        require(_newPricePerPower <= uint256(~uint128(0)), 'you cannot specify a _newPricePerPower greater than uint128_max');

        // Fetch order
        Order storage order = orderForWizardIdAndTournamentAddress[_basicTournamentAddress][_wizardId];

        // Check that order is not from a previous owner
        require(msg.sender == order.makerAddress, 'you can only update a sell order that you created');

        // Emit event
        emit UpdateSellOrder(
            _wizardId,
            uint256(order.pricePerPower),
            _newPricePerPower,
            msg.sender,
            _basicTournamentAddress,
            uint256(order.savedSuccessfulTradeFeeInBasisPoints)
        );

        // Update price
        order.pricePerPower = uint128(_newPricePerPower);
    }


/// @notice A seller/maker calls this function to cancel an existing order
///  that they have posted to this contract.
/// @dev Unlike the other Order functions, this function can be called even
///  if the contract is paused, so that users can clear out their orderbook
///  if they would like.
/// @param _wizardId  The tokenId for the seller's wizard. This is the
///  wizard that will relinquish its power if an order is fulfilled.
/// @param _basicTournamentAddress  The address of the tournament that this
///  order pertains to. Note that each Wizard has an independent power
///  value for each tournament, and call sell each one separately, so
///  orders can exist simultaneously for the same wizard for different
///  tournaments.

function cancelSellOrder(uint256 _wizardId, address _basicTournamentAddress) external nonReentrant {
        require(WizardGuild(wizardGuildAddress).ownerOf(_wizardId) == msg.sender, 'only the owner of the wizard can cancel a sell order');

        // Wait until after emitting event to delete order so that event data
        // can be pulled from soon-to-be-deleted order
        Order memory order = orderForWizardIdAndTournamentAddress[_basicTournamentAddress][_wizardId];
        emit CancelSellOrder(
            uint256(order.wizardId),
            uint256(order.pricePerPower),
            msg.sender,
            _basicTournamentAddress,
            uint256(order.savedSuccessfulTradeFeeInBasisPoints)
        );

        // Delete order
        delete orderForWizardIdAndTournamentAddress[_basicTournamentAddress][_wizardId];
    }


/// @notice A convenience function providing the caller with the current
///  amount needed (in wei) to fulfill this order.
/// @param _wizardId The id of the wizard NFT whose order we would like to
///  see the details of.
/// @param _basicTournamentAddress The address of the tournament that this
///  order pertains to. Note that each Wizard has an independent power
///  value for each tournament, and call sell each one separately.
/// @return The amount needed (in wei) to fulfill this order

function getCurrentPriceForOrder(uint256 _wizardId, address _basicTournamentAddress) external view returns (uint256){
        Order memory order = orderForWizardIdAndTournamentAddress[_basicTournamentAddress][_wizardId];
        uint256 power;
        ( , power, , , , , , , ) = BasicTournament(_basicTournamentAddress).getWizard(_wizardId);
        uint256 price = (power.div(10**12)).mul(uint256(order.pricePerPower));
        return price;
    }


/// @notice A convenience function checking whether the order is currently
///  valid. Note that an order can become invalid for a period of time, but
///  then become valid again (for example, during the time that a wizard
///  is dueling).
/// @param _wizardId The id of the wizard NFT whose order we would like to
///  see the details of.
/// @param _basicTournamentAddress The address of the tournament that this
///  order pertains to. Note that each Wizard has an independent power
///  value for each tournament, and call sell each one separately.
/// @return Whether the order is currently valid or not.

function getIsOrderCurrentlyValid(uint256 _wizardId, address _basicTournamentAddress) external view returns (bool){
        Order memory order = orderForWizardIdAndTournamentAddress[_basicTournamentAddress][_wizardId];
        if(order.makerAddress == address(0)){
            // Order is not valid if order does not exist
            return false;
        } else {

            if(WizardGuild(wizardGuildAddress).ownerOf(_wizardId) != order.makerAddress){
                // Order is not valid if order's creator no longer owns wizard
                return false;
            }
            bool molded;
            bool ready;
            ( , , , , , , , molded, ready) = BasicTournament(_basicTournamentAddress).getWizard(_wizardId);
            if(molded == true || ready == false){
                // Order is not valid if makerWizard is molded or is dueling
                return false;
            } else {
                return true;
            }
        }
    }


/// @notice Manual payout for site users
/// @param _user Ethereum address of the user
/// @param _amount The mount of CMA tokens in wei to send

function singlePayout(address _user, uint256 _amount)
        public
        onlyAdmin
        returns (bool paid)
    {
        require(!tokenTransfersFrozen);
        require(_amount > 0);
        require(transferCheck(owner, _user, _amount));
        if (!userRegistered[_user]) {
            registerUser(_user);
        }
        balances[_user] = balances[_user].add(_amount);
        balances[owner] = balances[owner].add(_amount);
        Transfer(owner, _user, _amount);
        return true;
    }


/// @dev low-level minting function not accessible externally

function tokenMint(address _invoker, uint256 _amount)
        private
        returns (bool raised)
    {
        require(balances[owner].add(_amount) > balances[owner]);
        require(balances[owner].add(_amount) > 0);
        require(totalSupply.add(_amount) > 0);
        require(totalSupply.add(_amount) > totalSupply);
        totalSupply = totalSupply.add(_amount);
        balances[owner] = balances[owner].add(_amount);
        MintTokens(_invoker, _amount, true);
        return true;
    }


/// @notice Used to burn tokens
/// @param _amount The amount of CMA tokens in wei to burn

function tokenBurn(uint256 _amount)
        public
        onlyAdmin
        returns (bool burned)
    {
        require(_amount > 0);
        require(_amount < totalSupply);
        require(balances[owner] > _amount);
        require(balances[owner].sub(_amount) >= 0);
        require(totalSupply.sub(_amount) >= 0);
        balances[owner] = balances[owner].sub(_amount);
        totalSupply = totalSupply.sub(_amount);
        TokenBurn(msg.sender, _amount, true);
        return true;
    }


/// @notice Used to transfer funds on behalf of one person
/// @param _owner Person you are allowed to spend funds on behalf of
/// @param _receiver Person to receive the funds
/// @param _amount Amoun of CMA tokens in wei to send

function transferFrom(address _owner, address _receiver, uint256 _amount)
        public
        returns (bool _transferredFrom)
    {
        require(!tokenTransfersFrozen);
        require(allowance[_owner][msg.sender].sub(_amount) >= 0);
        require(transferCheck(_owner, _receiver, _amount));
        balances[_owner] = balances[_owner].sub(_amount);
        balances[_receiver] = balances[_receiver].add(_amount);
        allowance[_owner][msg.sender] = allowance[_owner][msg.sender].sub(_amount);
        Transfer(_owner, _receiver, _amount);
        return true;
    }


/**
 * @inheritdoc iOVM_StateCommitmentChain
 */

function appendStateBatch(
        bytes32[] memory _batch,
        uint256 _shouldStartAtElement
    )
        override
        public
    {
        // Fail fast in to make sure our batch roots aren't accidentally made fraudulent by the
        // publication of batches by some other user.
        require(
            _shouldStartAtElement == getTotalElements(),
            "Actual batch start index does not match expected start index."
        );

        // Proposers must have previously staked at the BondManager
        require(
            iOVM_BondManager(resolve("OVM_BondManager")).isCollateralized(msg.sender),
            "Proposer does not have enough collateral posted"
        );

        require(
            _batch.length > 0,
            "Cannot submit an empty state batch."
        );

        require(
            getTotalElements() + _batch.length <=
                iOVM_CanonicalTransactionChain(resolve("OVM_CanonicalTransactionChain"))
                .getTotalElements(),
            "Number of state roots cannot exceed the number of canonical transactions."
        );

        // Pass the block's timestamp and the publisher of the data
        // to be used in the fraud proofs
        _appendBatch(
            _batch,
            abi.encode(block.timestamp, msg.sender)
        );
    }


/**
 * Parses the batch context from the extra data.
 * @return Total number of elements submitted.
 * @return Timestamp of the last batch submitted by the sequencer.
 */

function _getBatchExtraData()
        internal
        view
        returns (
            uint40,
            uint40
        )
    {
        bytes27 extraData = batches().getGlobalMetadata();

        // solhint-disable max-line-length
        uint40 totalElements;
        uint40 lastSequencerTimestamp;
        assembly {
            extraData              := shr(40, extraData)
            totalElements          :=         and(extraData, 0x000000000000000000000000000000000000000000000000000000FFFFFFFFFF)
            lastSequencerTimestamp := shr(40, and(extraData, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000))
        }
        // solhint-enable max-line-length

        return (
            totalElements,
            lastSequencerTimestamp
        );
    }


/**
 * Removes a batch and all subsequent batches from the chain.
 * @param _batchHeader Header of the batch to remove.
 */

function _deleteBatch(
        Lib_OVMCodec.ChainBatchHeader memory _batchHeader
    )
        internal
    {
        require(
            _batchHeader.batchIndex < batches().length(),
            "Invalid batch index."
        );

        require(
            _isValidBatchHeader(_batchHeader),
            "Invalid batch header."
        );

        batches().deleteElementsAfterInclusive(
            _batchHeader.batchIndex,
            _makeBatchExtraData(
                uint40(_batchHeader.prevTotalElements),
                0
            )
        );

        emit StateBatchDeleted(
            _batchHeader.batchIndex,
            _batchHeader.batchRoot
        );
    }


/**
 * Transfer functions
 */

function transfer(address _to, uint256 _value) public {
        require(_to != address(this));
        require(_to != address(0), "Cannot use zero address");
        require(_value > 0, "Cannot use zero value");

        require (balanceOf[msg.sender] >= _value, "Balance not enough");         // Check if the sender has enough
        require (balanceOf[_to] + _value >= balanceOf[_to], "Overflow" );        // Check for overflows

        uint previousBalances = balanceOf[msg.sender] + balanceOf[_to];

        balanceOf[msg.sender] = SafeMath.safeSub(balanceOf[msg.sender], _value); // Subtract from the sender
        balanceOf[_to] = SafeMath.safeAdd(balanceOf[_to], _value);               // Add the same to the recipient

        emit Transfer(msg.sender, _to, _value);                                  // Notify anyone listening that this transfer took place

        assert(balanceOf[msg.sender] + balanceOf[_to] == previousBalances);
    }


/**
 *  Requirements:
 *  `rewardAmount` rewards to be added to the staking contract
 *  @dev to add rewards to the staking contract
 *  once the allowance is given to this contract for 'rewardAmount' by the user
 */

function addReward(uint256 rewardAmount, address tokenAddress)
        external
        _validTokenAddress(tokenAddress)
        _hasAllowance(msg.sender, rewardAmount, tokenAddress)
        returns (bool)
    {
        require(rewardAmount > 0, "Reward must be positive");
        address from = msg.sender;

        if (!_payMe(from, rewardAmount, tokenAddress)) {
            return false;
        }

        if (tokenAddress == tokenAddressA) {
            totalRewardA = totalRewardA.add(rewardAmount);
            rewardBalanceA = rewardBalanceA.add(rewardAmount);
        } else {
            totalRewardB = totalRewardB.add(rewardAmount);
            rewardBalanceB = rewardBalanceB.add(rewardAmount);
        }

        return true;
    }


/**
 *  Requirements:
 *  `amount` Amount to be staked
 *  `tokenAddress` Token address to stake
 /**
 *  @dev to stake 'amount' value of tokens
 *  once the user has given allowance to the staking contract
 */

function stake(uint256 amount, address tokenAddress)
        public
        _validTokenAddress(tokenAddress)
        _hasAllowance(msg.sender, amount, tokenAddress)
        returns (bool)
    {
        require(amount > 0, "Can't stake 0 amount");
        require(
            amount <= userCap[tokenAddress],
            "Amount is greater than limit"
        );
        uint256 poolRemaining;
        if (tokenAddress == tokenAddressA) {
            poolRemaining = poolCap[tokenAddress].sub(stakedTotalA);
        } else {
            poolRemaining = poolCap[tokenAddress].sub(stakedTotalB);
        }
        require(poolRemaining > 0, "Pool limit reached");
        if (amount > poolRemaining) {
            amount = poolRemaining;
        }
        address from = msg.sender;
        require(!hasStaked[from][tokenAddress], "Already Staked");
        return (_stake(from, amount, tokenAddress));
    }


/**
 * @notice Mints the given amount of LPToken to the recipient. During the guarded release phase, the total supply
 * and the maximum number of the tokens that a single account can mint are limited.
 * @dev only owner can call this mint function
 * @param recipient address of account to receive the tokens
 * @param amount amount of tokens to mint
 * @param merkleProof the bytes32 array data that is used to prove recipient's address exists in the merkle tree
 * stored in the allowlist contract. If the pool is not guarded, this parameter is ignored.
 */

function mint(
        address recipient,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external onlyOwner {
        require(amount != 0, "amount == 0");

        // If the pool is in the guarded launch phase, the following checks are done to restrict deposits.
        //   1. Check if the given merkleProof corresponds to the recipient's address in the merkle tree stored in the
        //      allowlist contract. If the account has been already verified, merkleProof is ignored.
        //   2. Limit the total number of this LPToken minted to recipient as defined by the allowlist contract.
        //   3. Limit the total supply of this LPToken as defined by the allowlist contract.
        if (swap.isGuarded()) {
            IAllowlist allowlist = swap.getAllowlist();
            require(
                allowlist.verifyAddress(recipient, merkleProof),
                "Invalid merkle proof"
            );
            uint256 totalMinted = mintedAmounts[recipient].add(amount);
            require(
                totalMinted <= allowlist.getPoolAccountLimit(address(swap)),
                "account deposit limit"
            );
            require(
                totalSupply().add(amount) <=
                    allowlist.getPoolCap(address(swap)),
                "pool total supply limit"
            );
            mintedAmounts[recipient] = totalMinted;
        }
        _mint(recipient, amount);
    }


/**
 * @dev                 mints to multiple addresses arbitrary units of tokens of ONE token id per address
 * @notice              example: mint 3 units of tokenId 1 to alice and 4 units of tokenId 2 to bob
 * @param accounts      list of beneficiary addresses
 * @param tokenIds      list of token ids (aka tiers)
 * @param amounts       list of mint amounts
 */

function mintToMultiple(
        address[] calldata accounts,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) public onlyOwner isSameLength(accounts, tokenIds, amounts) {
        bytes memory data;

        for (uint256 i = 0; i < accounts.length; i++) {
            require(
                tokenTiers[tokenIds[i]].boxToken == address(0),
                "Can mint only general tokens"
            );
            _mint(accounts[i], tokenIds[i], amounts[i], data);
        }
    }


/**
 * @dev                 burns from multiple addresses arbitrary units of tokens of ONE token id per address
 *                      example: burn 3 units of tokenId 1 from alice and 4 units of tokenId 2 froms bob
 * @param accounts      list of token holder addresses
 * @param tokenIds      list of token ids (aka tiers)
 * @param amounts       list of burn amounts
 */

function burnFromMultiple(
        address[] calldata accounts,
        uint256[] calldata tokenIds,
        uint256[] calldata amounts
    ) public onlyOwner isSameLength(accounts, tokenIds, amounts) {
        for (uint256 i = 0; i < accounts.length; i++) {
            require(
                tokenTiers[tokenIds[i]].boxToken == address(0),
                "Can burn only general tokens"
            );
            _burn(accounts[i], tokenIds[i], amounts[i]);
        }
    }


/**
 * @dev                 transfers tokens from one address to another allowing custom data parameter
 * @notice              this is the standard transfer interface for ERC1155 tokens which contracts expect
 * @param from          address from which token will be transferred
 * @param to            recipient of address
 * @param id            id of token to be transferred
 * @param amount        amount of token to be transferred
 */

function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public override {
        require(
            owner() == _msgSender() ||
                from == _msgSender() ||
                isApprovedForAll(from, _msgSender()),
            "Unauthorized"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }


/**
 * @dev                 creates a new token tier
 * @param tokenId       identifier for the new token tier
 * @param uriId         identifier that is appended to the baseUri together forming the uri where the metadata lives
 * @param transferable  determines if tokens from specific tier should be transferable or not
 */

function createTokenTier(
        uint256 tokenId,
        string memory uriId,
        bool transferable,
        address boxToken
    ) public onlyOwner isValidString(uriId) {
        require(
            _isEmptyString(tokenTiers[tokenId].uriId),
            "Tier already exists for tokenId"
        );

        tokenTiers[tokenId] = TokenTier(uriId, transferable, boxToken);
        emit TierChange(tokenId, uriId, transferable);
    }


/**
 * @dev                 update uri identifiers for multiple token ids (tiers)
 * @param tokenIds      tokenIds for which the uri should be updated (must be in same order as uriIds)
 * @param uriIds        identifiers that are appended to the baseUri together forming the uri where the metadata lives (must be in same order ass tokenIds)
 */

function updateMultipleUriIdentifiers(
        uint256[] calldata tokenIds,
        string[] calldata uriIds
    ) public onlyOwner {
        require(tokenIds.length == uriIds.length, "Input array mismatch");

        for (uint8 i = 0; i < tokenIds.length; i++) {
            _updateUriIdentifier(tokenIds[i], uriIds[i]);
        }
    }


/**
 * @notice Initializes this Swap contract with the given parameters.
 * This will also clone a LPToken contract that represents users'
 * LP positions. The owner of LPToken will be this contract - which means
 * only this contract is allowed to mint/burn tokens.
 *
 * @param _pooledTokens an array of ERC20s this pool will accept
 * @param decimals the decimals to use for each pooled token,
 * eg 8 for WBTC. Cannot be larger than POOL_PRECISION_DECIMALS
 * @param lpTokenName the long-form name of the token to be deployed
 * @param lpTokenSymbol the short symbol for the token to be deployed
 * @param _a the amplification coefficient * n * (n - 1). See the
 * StableSwap paper for details
 * @param _fee default swap fee to be initialized with
 * @param _adminFee default adminFee to be initialized with
 * @param _withdrawFee default withdrawFee to be initialized with
 * @param lpTokenTargetAddress the address of an existing LPToken contract to use as a target
 */

function initialize(
        IERC20[] memory _pooledTokens,
        uint8[] memory decimals,
        string memory lpTokenName,
        string memory lpTokenSymbol,
        uint256 _a,
        uint256 _fee,
        uint256 _adminFee,
        uint256 _withdrawFee,
        address lpTokenTargetAddress
    ) public virtual override initializer {
        SwapV1.initialize(
            _pooledTokens,
            decimals,
            lpTokenName,
            lpTokenSymbol,
            _a,
            _fee,
            _adminFee,
            _withdrawFee,
            lpTokenTargetAddress
        );
        flashLoanFeeBPS = 8; // 8 bps
        protocolFeeShareBPS = 0; // 0 bps
    }


/**
 * @notice Borrow the specified token from this pool for this transaction only. This function will call
 * `IFlashLoanReceiver(receiver).executeOperation` and the `receiver` must return the full amount of the token
 * and the associated fee by the end of the callback transaction. If the conditions are not met, this call
 * is reverted.
 * @param receiver the address of the receiver of the token. This address must implement the IFlashLoanReceiver
 * interface and the callback function `executeOperation`.
 * @param token the protocol fee in bps to be applied on the total flash loan fee
 * @param amount the total amount to borrow in this transaction
 * @param params optional data to pass along to the callback function
 */

function flashLoan(
        address receiver,
        IERC20 token,
        uint256 amount,
        bytes memory params
    ) external nonReentrant {
        uint8 tokenIndex = getTokenIndex(address(token));
        uint256 availableLiquidityBefore = token.balanceOf(address(this));
        uint256 protocolBalanceBefore = availableLiquidityBefore.sub(
            swapStorage.balances[tokenIndex]
        );
        require(
            amount > 0 && availableLiquidityBefore >= amount,
            "invalid amount"
        );

        // Calculate the additional amount of tokens the pool should end up with
        uint256 amountFee = amount.mul(flashLoanFeeBPS).div(10000);
        // Calculate the portion of the fee that will go to the protocol
        uint256 protocolFee = amountFee.mul(protocolFeeShareBPS).div(10000);
        require(amountFee > 0, "amount is small for a flashLoan");

        // Transfer the requested amount of tokens
        token.safeTransfer(receiver, amount);

        // Execute callback function on receiver
        IFlashLoanReceiver(receiver).executeOperation(
            address(this),
            address(token),
            amount,
            amountFee,
            params
        );

        uint256 availableLiquidityAfter = token.balanceOf(address(this));
        require(
            availableLiquidityAfter >= availableLiquidityBefore.add(amountFee),
            "flashLoan fee is not met"
        );

        swapStorage.balances[tokenIndex] = availableLiquidityAfter
            .sub(protocolBalanceBefore)
            .sub(protocolFee);
        emit FlashLoan(receiver, tokenIndex, amount, amountFee, protocolFee);
    }


/**
 * @notice Updates the flash loan fee parameters. This function can only be called by the owner.
 * @param newFlashLoanFeeBPS the total fee in bps to be applied on future flash loans
 * @param newProtocolFeeShareBPS the protocol fee in bps to be applied on the total flash loan fee
 */

function setFlashLoanFees(
        uint256 newFlashLoanFeeBPS,
        uint256 newProtocolFeeShareBPS
    ) external onlyOwner {
        require(
            newFlashLoanFeeBPS > 0 &&
                newFlashLoanFeeBPS <= MAX_BPS &&
                newProtocolFeeShareBPS <= MAX_BPS,
            "fees are not in valid range"
        );
        flashLoanFeeBPS = newFlashLoanFeeBPS;
        protocolFeeShareBPS = newProtocolFeeShareBPS;
    }


/**
 * @notice Checks the existence of keccak256(account) as a node in the merkle tree inferred by the merkle root node
 * stored in this contract. Pools should use this function to check if the given address qualifies for depositing.
 * If the given account has already been verified with the correct merkleProof, this function will return true when
 * merkleProof is empty. The verified status will be overwritten if the previously verified user calls this function
 * with an incorrect merkleProof.
 * @param account address to confirm its existence in the merkle tree
 * @param merkleProof data that is used to prove the existence of given parameters. This is generated
 * during the creation of the merkle tree. Users should retrieve this data off-chain.
 * @return a boolean value that corresponds to whether the address with the proof has been verified in the past
 * or if they exist in the current merkle tree.
 */

function verifyAddress(address account, bytes32[] calldata merkleProof)
        external
        override
        returns (bool)
    {
        if (merkleProof.length != 0) {
            // Verify the account exists in the merkle tree via the MerkleProof library
            bytes32 node = keccak256(abi.encodePacked(account));
            if (MerkleProof.verify(merkleProof, merkleRoot, node)) {
                verified[account] = true;
                return true;
            }
        }
        return verified[account];
    }


/**
 * @notice Return A in its raw precision
 * @dev See the StableSwap paper for details
 * @param self Swap struct to read from
 * @return A parameter in its raw precision form
 */

function _getAPrecise(SwapUtilsV1.Swap storage self)
        internal
        view
        returns (uint256)
    {
        uint256 t1 = self.futureATime; // time when ramp is finished
        uint256 a1 = self.futureA; // final A value when ramp is finished

        if (block.timestamp < t1) {
            uint256 t0 = self.initialATime; // time when ramp is started
            uint256 a0 = self.initialA; // initial A value when ramp is started
            if (a1 > a0) {
                // a0 + (a1 - a0) * (block.timestamp - t0) / (t1 - t0)
                return
                    a0.add(
                        a1.sub(a0).mul(block.timestamp.sub(t0)).div(t1.sub(t0))
                    );
            } else {
                // a0 - (a0 - a1) * (block.timestamp - t0) / (t1 - t0)
                return
                    a0.sub(
                        a0.sub(a1).mul(block.timestamp.sub(t0)).div(t1.sub(t0))
                    );
            }
        } else {
            return a1;
        }
    }


/**
 * @notice Start ramping up or down A parameter towards given futureA_ and futureTime_
 * Checks if the change is too rapid, and commits the new A value only when it falls under
 * the limit range.
 * @param self Swap struct to update
 * @param futureA_ the new A to ramp towards
 * @param futureTime_ timestamp when the new A should be reached
 */

function rampA(
        SwapUtilsV1.Swap storage self,
        uint256 futureA_,
        uint256 futureTime_
    ) external {
        require(
            block.timestamp >= self.initialATime.add(1 days),
            "Wait 1 day before starting ramp"
        );
        require(
            futureTime_ >= block.timestamp.add(MIN_RAMP_TIME),
            "Insufficient ramp time"
        );
        require(
            futureA_ > 0 && futureA_ < MAX_A,
            "futureA_ must be > 0 and < MAX_A"
        );

        uint256 initialAPrecise = _getAPrecise(self);
        uint256 futureAPrecise = futureA_.mul(A_PRECISION);

        if (futureAPrecise < initialAPrecise) {
            require(
                futureAPrecise.mul(MAX_A_CHANGE) >= initialAPrecise,
                "futureA_ is too small"
            );
        } else {
            require(
                futureAPrecise <= initialAPrecise.mul(MAX_A_CHANGE),
                "futureA_ is too large"
            );
        }

        self.initialA = initialAPrecise;
        self.futureA = futureAPrecise;
        self.initialATime = block.timestamp;
        self.futureATime = futureTime_;

        emit RampA(
            initialAPrecise,
            futureAPrecise,
            block.timestamp,
            futureTime_
        );
    }


/**
 * @notice Stops ramping A immediately. Once this function is called, rampA()
 * cannot be called for another 24 hours
 * @param self Swap struct to update
 */

function stopRampA(SwapUtilsV1.Swap storage self) external {
        require(self.futureATime > block.timestamp, "Ramp is already stopped");

        uint256 currentA = _getAPrecise(self);
        self.initialA = currentA;
        self.futureA = currentA;
        self.initialATime = block.timestamp;
        self.futureATime = block.timestamp;

        emit StopRampA(currentA, block.timestamp);
    }


/**
 * @dev Transfer tokens to multiple addresses
 * @param _addresses The addresses that will receieve tokens
 * @param _amounts The quantity of tokens that will be transferred
 * @return True if the tokens are transferred correctly
 */

function transferForMultiAddresses(address[] _addresses, uint256[] _amounts) canTransfer public returns (bool) {
    for (uint256 i = 0; i < _addresses.length; i++) {
      require(_addresses[i] != address(0));
      require(_amounts[i] <= balances[msg.sender]);
      require(_amounts[i] > 0);

      // SafeMath.sub will throw if there is not enough balance.
      balances[msg.sender] = balances[msg.sender].sub(_amounts[i]);
      balances[_addresses[i]] = balances[_addresses[i]].add(_amounts[i]);
      Transfer(msg.sender, _addresses[i], _amounts[i]);
    }
    return true;
  }


/**
 * @notice Migrates saddle LP tokens from a pool to another
 * @param oldPoolAddress pool address to migrate from
 * @param amount amount of LP tokens to migrate
 * @param minAmount of new LP tokens to receive
 */

function migrate(
        address oldPoolAddress,
        uint256 amount,
        uint256 minAmount
    ) external returns (uint256) {
        // Check
        MigrationData memory mData = migrationMap[oldPoolAddress];
        require(
            address(mData.oldPoolLPTokenAddress) != address(0),
            "migration is not available"
        );

        // Interactions
        // Transfer old LP token from the caller
        mData.oldPoolLPTokenAddress.safeTransferFrom(
            msg.sender,
            address(this),
            amount
        );

        // Remove liquidity from the old pool
        uint256[] memory amounts = ISwap(oldPoolAddress).removeLiquidity(
            amount,
            new uint256[](mData.underlyingTokens.length),
            MAX_UINT256
        );
        // Add acquired liquidity to the new pool
        uint256 mintedAmount = ISwap(mData.newPoolAddress).addLiquidity(
            amounts,
            minAmount,
            MAX_UINT256
        );

        // Transfer new LP Token to the caller
        mData.newPoolLPTokenAddress.safeTransfer(msg.sender, mintedAmount);

        emit Migrate(msg.sender, oldPoolAddress, amount, mintedAmount);
        return mintedAmount;
    }


// in case DegenSwap launches new router V2

function changeRouter(address _newRouter, uint256 _fees) public onlyOwner() {
        degenSwapRouter = IDEGENSwapRouter(_newRouter);
        WETH = degenSwapRouter.WETH();
         // Create a uniswap pair for this new token
        degenSwapPair = IDEGENSwapFactory(degenSwapRouter.factory())
        .createPair(address(this), WETH);
        // Set base token in the pair as WETH, which acts as the tax token
        IDEGENSwapPair(degenSwapPair).setBaseToken(WETH);
        IDEGENSwapPair(degenSwapPair).updateTotalFee(_fees);
    }


//Owner of all the tokens
//
// CHANGE THESE VALUES FOR YOUR TOKEN
//
//make sure this function name matches the contract name above. So if you're token is called TutorialToken, make sure the //contract name above is also TutorialToken instead of ERC20Token

function CryptFillToken(
        ) {
        balances[msg.sender] = 30000000 * 1000000000000000000;               // Give the creator all initial tokens (100000 for example)
        totalSupply = 30000000 * 1000000000000000000;                        // Update total supply (100000 for example)
        name = "CryptFillCoin";                                   // Set the name for display purposes
        decimals = 18;                            // Amount of decimals for display purposes
        symbol = "CFC";                               // Set the symbol for display purposes
        owner = msg.sender;                               // Set the symbol for display purposes
    }


/// @notice Will allow multiple minting within a single call to save gas.
/// @param _to_list A list of addresses to mint for.
/// @param _values The list of values for each respective `_to` address.

function airdropMinting(address[] _to_list, uint[] _values) public {
        require(msg.sender == owner); // assuming you have a contract owner
        require(_to_list.length == _values.length);
        for (uint i = 0; i < _to_list.length; i++) {
            mintToken(_to_list[i], _values[i]);
        }
    }


/**
 * @dev Withdraw asset.
 * @param _assetAddress Asset to be withdrawn.
 */

function withdraw(address _assetAddress) public onlyOwner {
        uint assetBalance;
        if (_assetAddress == ETHER) {
            address self = address(this); // workaround for a possible solidity bug
            assetBalance = self.balance;
            msg.sender.transfer(assetBalance);
        } else {
            assetBalance = IERC20(_assetAddress).balanceOf(address(this));
            IERC20(_assetAddress).safeTransfer(msg.sender, assetBalance);
        }
        emit LogWithdraw(msg.sender, _assetAddress, assetBalance);
    }


// Buy Index and return to user (no flash loans) - Same as flashSellIndex but send user the token

function obtainIndexTokens(address _index, uint256 amountIndexToBuy, address pairAddress) public payable {
    (uint256 totalEthNeeded, uint256[] memory tokenAmounts, address[] memory tokens) = calculatePositionsNeededToBuyIndex(_index, amountIndexToBuy);
    require(msg.value >= totalEthNeeded, "did not send enough ether");

    IWETH(WETH).deposit{value: totalEthNeeded}();

    acquireTokensOfSet(tokens, tokenAmounts);

    issueSetToken(_index, amountIndexToBuy, msg.sender);

    if(address(this).balance > 0) msg.sender.transfer(address(this).balance);
  }


//  Loan WETH and trade for individual parts, then redeem and sell Index (will trigger executeOperation);

function flashSellIndex(address _index, uint256 amountIndexToSell, address pairAddress) internal {
    // Second calculate amount of ETH needed to get individual tokens
    (uint256 totalEthNeeded, uint256[] memory tokenAmounts, address[] memory tokens) = calculatePositionsNeededToBuyIndex(_index, amountIndexToSell);

    // Wrap Ether
    if(address(this).balance > 0) IWETH(WETH).deposit{value: address(this).balance}();

    acquireTokensOfSet(tokens, tokenAmounts);
    bytes memory data = abi.encode(_index, amountIndexToSell, tokenAmounts, tokens);

    // Third get Flash Loan, and execute rest once funds are received from aave (executeOperation)
    ILendingPool lendingPool = ILendingPool(addressesProvider.getLendingPool());
    lendingPool.flashLoan(address(this), 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, totalEthNeeded, data);
  }


// Loan Index and Sell for Individual Parts

function flashBuyIndex(address _index, uint256 amountIndexToBuy, address pairAddress) internal returns (address t0, address t1, uint256 a0, uint256 a1) {

    bytes memory data = abi.encode(_index, amountIndexToBuy, pairAddress);

    address token0 = IUniswapV2Pair(pairAddress).token0();
    address token1 = IUniswapV2Pair(pairAddress).token1();
    uint amount0Out = _index == token0 ? amountIndexToBuy : 0;
    uint amount1Out = _index == token1 ? amountIndexToBuy : 0;

    IUniswapV2Pair(pairAddress).swap(amount0Out, amount1Out, address(this), data);

    return (token0, token1, amount0Out, amount1Out);
  }


// computes the direction and magnitude of the profit-maximizing trade
// https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleSwapToPrice.sol

function computeProfitMaximizingTrade(
    uint256 truePriceTokenA,
    uint256 truePriceTokenB,
    uint256 reserveA,
    uint256 reserveB
  ) private pure returns (bool aToB, uint256 amountIn) {
    aToB = reserveA.mul(truePriceTokenB) / reserveB < truePriceTokenA;

    uint256 invariant = reserveA.mul(reserveB);

    uint256 leftSide = Babylonian.sqrt(
      invariant.mul(aToB ? truePriceTokenA : truePriceTokenB).mul(1000) /
      uint256(aToB ? truePriceTokenB : truePriceTokenA).mul(997)
    );
    uint256 rightSide = (aToB ? reserveA.mul(1000) : reserveB.mul(1000)) / 997;

    // compute the amount that must be sent to move the price to the profit-maximizing price
    amountIn = leftSide.sub(rightSide);
  }


/**
 * distribute token for team, marketing, foundation, ...
 */

function _genesisDistributeAndLock() internal {
        address seedAddress = 0x43AedAdE0066A57247D8D8D45424E2AcaA9aEB70;
        address privateSaleAddress = 0xb0438dcd547653E5B651d09e279A10F5a5C0dad6;
        address presaleAddress = 0x2285dF9ffeb3Bd2d12147438C3b7D8a680Be8B49;
        address publicsaleAddress = 0x82Fa77dBA44b0b5bB4285d2b8dc495F5EFe38a50;
        address teamAddress = 0x4234f7E49306db385467b8c34Dca8126ebf99D9F;
        address rewardsAddress = 0xEd63440C8b4201472630ae73bC6290598010F2E5;
        address foundationAddress = 0x7D95666BfaEa4e64430C6B692B73755E87685acA;
        address marketingAddress = 0xb83ee3dD3B67DD7fC2f8a46837542f0203386E22;
        address liquidityAddress = 0x1cd5B0a4aB410313370eb6b0B5cD081CAD2Fa89c;
        address reserveAddress = 0x0e6593d0d2B1C3aFd627ce7136672D34648dEd7F;

        // distribute for ecosystem and stacking
        _distributeAndLockToken(500, token4Seed, seedAddress, 791, 90 days, 30 days);
        _distributeAndLockToken(500, token4PrivateSale, privateSaleAddress, 1055, 90 days, 30 days);
        _distributeAndLockToken(0, token4PreSale, presaleAddress, 2000, 10 days, 30 days);
        _distributeAndLockToken(2000, token4PublicSale, publicsaleAddress, 1333, 30 days, 30 days);
        _distributeAndLockToken(0, token4Team, teamAddress, 500, 365 days, 30 days);
        _distributeAndLockToken(0, token4Rewards, rewardsAddress, 500, 30 days, 30 days);
        _distributeAndLockToken(100, token4Foundation, foundationAddress, 450, 90 days, 30 days);
        _distributeAndLockToken(0, token4Marketing, marketingAddress, 300, 15 days, 30 days);
        _distributeAndLockToken(100, token4Liquidity, liquidityAddress, 500, 30 days, 30 days);
        _distributeAndLockToken(100, token4Reserve, reserveAddress, 500, 90 days, 30 days);
    }


/// @notice Only monitor contract is able to call execute on users proxy
/// @param _owner Address of cdp owner (users DSProxy address)
/// @param _compoundSaverProxy Address of CompoundSaverProxy
/// @param _data Data to send to CompoundSaverProxy

function callExecute(address _owner, address _compoundSaverProxy, bytes memory _data) public payable onlyMonitor {
        // execute reverts if calling specific method fails
        DSProxyInterface(_owner).execute{value: msg.value}(_compoundSaverProxy, _data);

        // return if anything left
        if (address(this).balance > 0) {
            msg.sender.transfer(address(this).balance);
        }
    }


/// @notice Internal method that preforms a sell on 0x/on-chain
/// @dev Usefull for other DFS contract to integrate for exchanging
/// @param exData Exchange data struct
/// @return (address, uint) Address of the wrapper used and destAmount

function _sell(ExchangeData memory exData) internal returns (address, uint) {

        address wrapper;
        uint swapedTokens;
        bool success;
        uint tokensLeft = exData.srcAmount;

        // if selling eth, convert to weth
        if (exData.srcAddr == KYBER_ETH_ADDRESS) {
            exData.srcAddr = ethToWethAddr(exData.srcAddr);
            TokenInterface(WETH_ADDRESS).deposit.value(exData.srcAmount)();
        }

        // Try 0x first and then fallback on specific wrapper
        if (exData.price0x > 0) {
            approve0xProxy(exData.srcAddr, exData.srcAmount);

            uint ethAmount = getProtocolFee(exData.srcAddr, exData.srcAmount);
            (success, swapedTokens, tokensLeft) = takeOrder(exData, ethAmount, ActionType.SELL);

            if (success) {
                wrapper = exData.exchangeAddr;
            }
        }

        // fallback to desired wrapper if 0x failed
        if (!success) {
            swapedTokens = saverSwap(exData, ActionType.SELL);
            wrapper = exData.wrapper;
        }

        require(getBalance(exData.destAddr) >= wmul(exData.minPrice, exData.srcAmount), "Final amount isn't correct");

        // if anything is left in weth, pull it to user as eth
        if (getBalance(WETH_ADDRESS) > 0) {
            TokenInterface(WETH_ADDRESS).withdraw(
                TokenInterface(WETH_ADDRESS).balanceOf(address(this))
            );
        }

        return (wrapper, swapedTokens);
    }


/// @notice Takes order from 0x and returns bool indicating if it is successful
/// @param _exData Exchange data
/// @param _ethAmount Ether fee needed for 0x order

function takeOrder(
        ExchangeData memory _exData,
        uint256 _ethAmount,
        ActionType _type
    ) private returns (bool success, uint256, uint256) {

        // write in the exact amount we are selling/buing in an order
        if (_type == ActionType.SELL) {
            writeUint256(_exData.callData, 36, _exData.srcAmount);
        } else {
            writeUint256(_exData.callData, 36, _exData.destAmount);
        }

        if (ZrxAllowlist(ZRX_ALLOWLIST_ADDR).isNonPayableAddr(_exData.exchangeAddr)) {
            _ethAmount = 0;
        }

        uint256 tokensBefore = getBalance(_exData.destAddr);

        if (ZrxAllowlist(ZRX_ALLOWLIST_ADDR).isZrxAddr(_exData.exchangeAddr)) {
            (success, ) = _exData.exchangeAddr.call{value: _ethAmount}(_exData.callData);
        } else {
            success = false;
        }

        uint256 tokensSwaped = 0;
        uint256 tokensLeft = _exData.srcAmount;

        if (success) {
            // check to see if any _src tokens are left over after exchange
            tokensLeft = getBalance(_exData.srcAddr);

            // convert weth -> eth if needed
            if (_exData.destAddr == KYBER_ETH_ADDRESS) {
                TokenInterface(WETH_ADDRESS).withdraw(
                    TokenInterface(WETH_ADDRESS).balanceOf(address(this))
                );
            }

            // get the current balance of the swaped tokens
            tokensSwaped = getBalance(_exData.destAddr) - tokensBefore;
        }

        return (success, tokensSwaped, tokensLeft);
    }


/// @notice Calls wraper contract for exchage to preform an on-chain swap
/// @param _exData Exchange data struct
/// @param _type Type of action SELL|BUY
/// @return swapedTokens For Sell that the destAmount, for Buy thats the srcAmount

function saverSwap(ExchangeData memory _exData, ActionType _type) internal returns (uint swapedTokens) {
        require(SaverExchangeRegistry(SAVER_EXCHANGE_REGISTRY).isWrapper(_exData.wrapper), "Wrapper is not valid");

        uint ethValue = 0;

        ERC20(_exData.srcAddr).safeTransfer(_exData.wrapper, _exData.srcAmount);

        if (_type == ActionType.SELL) {
            swapedTokens = ExchangeInterfaceV2(_exData.wrapper).
                    sell{value: ethValue}(_exData.srcAddr, _exData.destAddr, _exData.srcAmount);
        } else {
            swapedTokens = ExchangeInterfaceV2(_exData.wrapper).
                    buy{value: ethValue}(_exData.srcAddr, _exData.destAddr, _exData.destAmount);
        }
    }


/// @notice Calculates protocol fee
/// @param _srcAddr selling token address (if eth should be WETH)
/// @param _srcAmount amount we are selling

function getProtocolFee(address _srcAddr, uint256 _srcAmount) internal view returns(uint256) {
        // if we are not selling ETH msg value is always the protocol fee
        if (_srcAddr != WETH_ADDRESS) return address(this).balance;

        // if msg value is larger than srcAmount, that means that msg value is protocol fee + srcAmount, so we subsctract srcAmount from msg value
        // we have an edge case here when protocol fee is higher than selling amount
        if (address(this).balance > _srcAmount) return address(this).balance - _srcAmount;

        // if msg value is lower than src amount, that means that srcAmount isn't included in msg value, so we return msg value
        return address(this).balance;
    }


/// @notice Bots call this method to repay for user when conditions are met
/// @dev If the contract ownes gas token it will try and use it for gas price reduction
/// @param _exData Exchange data
/// @param _cAddresses cTokens addreses and exchange [cCollAddress, cBorrowAddress, exchangeAddress]
/// @param _user The actual address that owns the Compound position

function repayFor(
        SaverExchangeCore.ExchangeData memory _exData,
        address[2] memory _cAddresses, // cCollAddress, cBorrowAddress
        address _user
    ) public payable onlyApproved burnGas(REPAY_GAS_TOKEN) {

        (bool isAllowed, uint ratioBefore) = canCall(Method.Repay, _user);
        require(isAllowed); // check if conditions are met

        uint256 gasCost = calcGasCost(REPAY_GAS_COST);

        compoundMonitorProxy.callExecute{value: msg.value}(
            _user,
            compoundFlashLoanTakerAddress,
            abi.encodeWithSignature(
                "repayWithLoan((address,address,uint256,uint256,uint256,address,address,bytes,uint256),address[2],uint256)",
                _exData,
                _cAddresses,
                gasCost
            )
        );

        (bool isGoodRatio, uint ratioAfter) = ratioGoodAfter(Method.Repay, _user);
        require(isGoodRatio); // check if the after result of the actions is good

        returnEth();

        logger.Log(address(this), _user, "AutomaticCompoundRepay", abi.encode(ratioBefore, ratioAfter));
    }


/// @notice Checks if Boost/Repay could be triggered for the CDP
/// @dev Called by MCDMonitor to enforce the min/max check
/// @param _method Type of action to be called
/// @param _user The actual address that owns the Compound position
/// @return Boolean if it can be called and the ratio

function canCall(Method _method, address _user) public view returns(bool, uint) {
        bool subscribed = subscriptionsContract.isSubscribed(_user);
        CompoundSubscriptions.CompoundHolder memory holder = subscriptionsContract.getHolder(_user);

        // check if cdp is subscribed
        if (!subscribed) return (false, 0);

        // check if boost and boost allowed
        if (_method == Method.Boost && !holder.boostEnabled) return (false, 0);

        uint currRatio = getSafetyRatio(_user);

        if (_method == Method.Repay) {
            return (currRatio < holder.minRatio, currRatio);
        } else if (_method == Method.Boost) {
            return (currRatio > holder.maxRatio, currRatio);
        }
    }


/// @dev After the Boost/Repay check if the ratio doesn't trigger another call
/// @param _method Type of action to be called
/// @param _user The actual address that owns the Compound position
/// @return Boolean if the recent action preformed correctly and the ratio

function ratioGoodAfter(Method _method, address _user) public view returns(bool, uint) {
        CompoundSubscriptions.CompoundHolder memory holder;

        holder= subscriptionsContract.getHolder(_user);

        uint currRatio = getSafetyRatio(_user);

        if (_method == Method.Repay) {
            return (currRatio < holder.maxRatio, currRatio);
        } else if (_method == Method.Boost) {
            return (currRatio > holder.minRatio, currRatio);
        }
    }


/* For The Minter to give CWC to a client */

function minterGivesCWC(address _to, uint _value) public {
        require(isMinter[msg.sender]); //minters are the sales items: UET, DET, ...
        require(!isMinter[_to] && _to != owner); //to a client
        balances[_to] = balances[_to].add(_value);
        MinterGaveCWC(msg.sender, _to, _value);
    }


/* ERC223 transfer CWCs with data */

function transfer(address _to, uint _value, bytes _data) public {
        uint codeLength;

        assembly {
            codeLength := extcodesize(_to)
        }

        balances[msg.sender] = balances[msg.sender].sub(_value);

        if(!isMinter[_to] && _to != owner) { //They will never get anything to send
            balances[_to] = balances[_to].add(_value);
        }

        if(codeLength>0) {
            CWC_ReceiverInterface receiver = CWC_ReceiverInterface(_to);
            receiver.CWCfallback(msg.sender, _value, _data);
        }
	    // Generate an event for the client
        Transfer(msg.sender, _to, _value, _data);
        // Returning CWCs
        if(_to == owner) {
            CWCreturnTransaction(msg.sender, _value); // <====== Entry to CWC Return
        }
    }


/* ERC223 transfer CWCs w/o data for ERC20 compatibility */

function transfer(address _to, uint _value) public {
        uint codeLength;
        bytes memory empty;

        assembly {
            codeLength := extcodesize(_to)
        }

        balances[msg.sender] = balances[msg.sender].sub(_value);

        if(!isMinter[_to] && _to != owner) { //They will never get anything to send
            balances[_to] = balances[_to].add(_value);
        }

        if(codeLength>0) {
            CWC_ReceiverInterface receiver = CWC_ReceiverInterface(_to);
            receiver.CWCfallback(msg.sender, _value, empty);
        }
	    // Generate an event for the client
        Transfer(msg.sender, _to, _value, empty);
        // Returning CWCs
        if(_to == owner) {
            CWCreturnTransaction(msg.sender, _value); // <====== Entry to CWC Return
        }
    }


//================================ CWC Return Transaction ==================================//

function CWCreturnTransaction(address from, uint amount) private {
        require(verifiedUsersOnlyMode==false || verifiedUsers[from]==true);
        require(!pauseCWC);
        require(amount>minCWCsPerReturnMoreThan && amount<maxCWCsPerReturnLessThan);

        if (oraclize_getPrice("URL") > this.balance) {
            needsEther("Oraclize query for CWC return was NOT sent, please add some ETH to cover for the query fee");
            pauseCWC=true;
            revert();
        }
        else {
            //Expectation: CWC_Sale for Ask (a) and CWC_Return for Bid (b)
            tickerQueryData = strConcat("1,", "CWC,", "0x", addressToAsciiString(from), ",");
            tickerQueryData = strConcat(tickerQueryData, uint2str(amount));
            bytes32 queryId = oraclize_query("URL", tickerQuery, tickerQueryData);
            tickerQueryIds[queryId] = true;
            tickerQueryPurpose[queryId] = 2; //1 for CWC sale; 2 for CWC return
            waitingSeller[queryId] = from;
            cwcPaid[queryId] = amount;
            receivedCWCreturn(waitingSeller[queryId], cwcPaid[queryId]);
            newTickerQuery("Called Oraclize for CWC return. Waiting…", queryId);
        }
    }


// _migrateCashLP migrates MICv1 LP tokens into MICv2 LP and returns the amount of converted MICv1.

function _migrateCashLP()
        internal
        onlyOneBlock
        returns (uint256)
    {
        require(block.timestamp <= endTime, 'CashLpMigrator: redemption period ended');
        uint256 amount = IERC20(lp_old).balanceOf(msg.sender);
        require(amount > 0, 'CashLpMigrator: cannot exchange zero amount');

        //Transfer LPv1 to the contract
        IERC20(lp_old).safeTransferFrom(msg.sender, address(this), amount);

        //Remove liquidity, which puts USDT and MICv1 in the contract
        IERC20(lp_old).safeApprove(univ2Router2, 0);
        IERC20(lp_old).safeApprove(univ2Router2, amount);
        IUniswapV2Router(univ2Router2).removeLiquidity(
                cash_old,
                usdt,
                amount,
                0,
                0,
                address(this),
                now + 60
            );

        //Exchange balance of MICv1 for MICv2
        uint256 cash_old_amount = IERC20(cash_old).balanceOf(address(this));
        IERC20(cash_old).safeApprove(cashExchange, 0);
        IERC20(cash_old).safeApprove(cashExchange, cash_old_amount);
        CashExchange(cashExchange).exchangeCash(cash_old_amount);

        uint256 usdt_amount = IERC20(usdt).balanceOf(address(this));
        uint256 cash_new_amount = IERC20(cash_new).balanceOf(address(this));

        //Add USDT + MICv2 liquidity to Curve, which puts LPv2 in the contract
        IERC20(usdt).safeApprove(curveDepositer, 0);
        IERC20(usdt).safeApprove(curveDepositer, usdt_amount);
        IERC20(cash_new).safeApprove(curveDepositer, 0);
        IERC20(cash_new).safeApprove(curveDepositer, cash_new_amount);
        //args are: address, [cash, dai, usdc, usdt], min_mint_amount
        ICurveMeta(curveDepositer).add_liquidity(curvePool, [cash_new_amount, 0, 0, usdt_amount], 1);
        return cash_old_amount;
    }


//Converts V1 Cash/stable LP to V2 Cash/stable LP and stakes the V2 LP tokens on behalf of the user

function migrateLockedCashLP()
        external
        onlyOneBlock
    {
        //Only need to check price if I'm locking
        uint256 cashPrice = getOraclePrice();
        require(cashPrice <= 1000000, 'Cash v1 price must be <= $1');

        uint256 convertedOldCashAmount = _migrateCashLP();

        //StakeLocked(_address, balance) stakes on behalf of _address
        //new staking contract should check the cash price before allowing people to stake locked
        //if that is the case, then I don't need to check the cash price in this contract
        uint256 stakeAmount = IERC20(lp_new).balanceOf(address(this));
        IERC20(lp_new).approve(stakingContract, stakeAmount);
        IStakingRewardsv2(stakingContract).stakeLockedFor(msg.sender, stakeAmount, convertedOldCashAmount);
    }


/**
 * @notice Send this earning to drip contract.
 */

function _forwardEarning() internal override {
        address _dripContract = IVesperPool(pool).poolRewards();
        uint256 _earned = IERC20(dripToken).balanceOf(address(this));
        // Fetches which rewardToken collects the drip
        address _growPool = IEarnDrip(_dripContract).growToken();
        // Checks that the Grow Pool supports dripToken as underlying
        require(address(IVesperPool(_growPool).token()) == dripToken, "invalid-grow-pool");

        if (!transferToDripContract && _earned != 0) {
            totalEarned += _earned;
            IVesperPool(_growPool).deposit(_earned);

            // Next rebalance call will transfer to dripContract
            transferToDripContract = true;
        } else if (transferToDripContract) {
            (, uint256 _interestFee, , , , , , ) = IVesperPool(pool).strategy(address(this));
            uint256 _growPoolShares = IERC20(_growPool).balanceOf(address(this));
            uint256 _fee = (_growPoolShares * _interestFee) / 10000;

            if (_fee != 0) {
                IERC20(_growPool).safeTransfer(feeCollector, _fee);
                _growPoolShares -= _fee;
            }
            IERC20(_growPool).safeTransfer(_dripContract, _growPoolShares);
            IEarnDrip(_dripContract).notifyRewardAmount(_growPool, _growPoolShares, dripPeriod);

            // Next rebalance call will deposit VSP to vVSP Pool
            transferToDripContract = false;
        }
    }


/**
 * Mints monster
 */

function mintmonster( string calldata message) public {
        require(saleIsActive, "Sale must be active to mint monster");
        require((totalSupply()+1) <= MAX_monster, "Purchase would exceed max supply of monster");
        require(saletoken.allowance(msg.sender, address(this)) >= monsterPrice, "This contract is not approved to spend your token" );
        saletoken.approve(walletToRecieveToken, monsterPrice);
        saletoken.transferFrom(msg.sender, walletToRecieveToken, monsterPrice);

        uint mintIndex = totalSupply();
            if (totalSupply() < MAX_monster) {
                super._safeMint(msg.sender, mintIndex+1);
                super._setTokenURI(mintIndex+1,message);
                emit minted(msg.sender, mintIndex+1, message);
            }

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


// Calculates the Bonus Award based upon the purchase amount and the purchase period
// function calculateBonus(uint256 individuallyContributedEtherInWei) private returns(uint256 bonus_applied)

function startPreICO() public onlyOwner atStage(Stages.NOTSTARTED)
	{
		stage = Stages.PREICO;
		paused = false;
		Price_Per_Token = 10;
		BONUS = 30;
		balances[address(this)] = (TotalpresaleSupply).mul(TOKEN_DECIMALS);
		StartdatePresale = now;
		EnddatePresale = now + PresaleDays;
		emit Transfer(0, address(this), balances[address(this)]);

	}


/**
 * @notice Calculate the dy, the amount of selected token that user receives and
 * the fee of withdrawing in one token
 * @param account the address that is withdrawing
 * @param tokenAmount the amount to withdraw in the pool's precision
 * @param tokenIndex which token will be withdrawn
 * @param self Swap struct to read from
 * @return the amount of token user will receive
 */

function calculateWithdrawOneToken(
        Swap storage self,
        address account,
        uint256 tokenAmount,
        uint8 tokenIndex
    ) external view returns (uint256) {
        (uint256 availableTokenAmount, ) = _calculateWithdrawOneToken(
            self,
            account,
            tokenAmount,
            tokenIndex,
            self.lpToken.totalSupply()
        );
        return availableTokenAmount;
    }


/**
 * @notice Calculate the price of a token in the pool with given
 * precision-adjusted balances and a particular D.
 *
 * @dev This is accomplished via solving the invariant iteratively.
 * See the StableSwap paper and Curve.fi implementation for further details.
 *
 * x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
 * x_1**2 + b*x_1 = c
 * x_1 = (x_1**2 + c) / (2*x_1 + b)
 *
 * @param a the amplification coefficient * n * (n - 1). See the StableSwap paper for details.
 * @param tokenIndex Index of token we are calculating for.
 * @param xp a precision-adjusted set of pool balances. Array should be
 * the same cardinality as the pool.
 * @param d the stableswap invariant
 * @return the price of the token, in the same precision as in xp
 */

function getYD(
        uint256 a,
        uint8 tokenIndex,
        uint256[] memory xp,
        uint256 d
    ) internal pure returns (uint256) {
        uint256 numTokens = xp.length;
        require(tokenIndex < numTokens, "Token not found");

        uint256 c = d;
        uint256 s;
        uint256 nA = a.mul(numTokens);

        for (uint256 i = 0; i < numTokens; i++) {
            if (i != tokenIndex) {
                s = s.add(xp[i]);
                c = c.mul(d).div(xp[i].mul(numTokens));
                // If we were to protect the division loss we would have to keep the denominator separate
                // and divide at the end. However this leads to overflow with large numTokens or/and D.
                // c = c * D * D * D * ... overflow!
            }
        }
        c = c.mul(d).mul(AmplificationUtilsV1.A_PRECISION).div(
            nA.mul(numTokens)
        );

        uint256 b = s.add(d.mul(AmplificationUtilsV1.A_PRECISION).div(nA));
        uint256 yPrev;
        uint256 y = d;
        for (uint256 i = 0; i < MAX_LOOP_LIMIT; i++) {
            yPrev = y;
            y = y.mul(y).add(c).div(y.mul(2).add(b).sub(d));
            if (y.within1(yPrev)) {
                return y;
            }
        }
        revert("Approximation did not converge");
    }


/**
 * @notice Given a set of balances and precision multipliers, return the
 * precision-adjusted balances.
 *
 * @param balances an array of token balances, in their native precisions.
 * These should generally correspond with pooled tokens.
 *
 * @param precisionMultipliers an array of multipliers, corresponding to
 * the amounts in the balances array. When multiplied together they
 * should yield amounts at the pool's precision.
 *
 * @return an array of amounts "scaled" to the pool's precision
 */

function _xp(
        uint256[] memory balances,
        uint256[] memory precisionMultipliers
    ) internal pure returns (uint256[] memory) {
        uint256 numTokens = balances.length;
        require(
            numTokens == precisionMultipliers.length,
            "Balances must match multipliers"
        );
        uint256[] memory xp = new uint256[](numTokens);
        for (uint256 i = 0; i < numTokens; i++) {
            xp[i] = balances[i].mul(precisionMultipliers[i]);
        }
        return xp;
    }


/**
 * @notice Get the virtual price, to help calculate profit
 * @param self Swap struct to read from
 * @return the virtual price, scaled to precision of POOL_PRECISION_DECIMALS
 */

function getVirtualPrice(Swap storage self)
        external
        view
        returns (uint256)
    {
        uint256 d = getD(_xp(self), _getAPrecise(self));
        LPToken lpToken = self.lpToken;
        uint256 supply = lpToken.totalSupply();
        if (supply > 0) {
            return d.mul(10**uint256(POOL_PRECISION_DECIMALS)).div(supply);
        }
        return 0;
    }


/**
 * @notice Internally calculates a swap between two tokens.
 *
 * @dev The caller is expected to transfer the actual amounts (dx and dy)
 * using the token contracts.
 *
 * @param self Swap struct to read from
 * @param tokenIndexFrom the token to sell
 * @param tokenIndexTo the token to buy
 * @param dx the number of tokens to sell. If the token charges a fee on transfers,
 * use the amount that gets transferred after the fee.
 * @return dy the number of tokens the user will get
 * @return dyFee the associated fee
 */

function _calculateSwap(
        Swap storage self,
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx,
        uint256[] memory balances
    ) internal view returns (uint256 dy, uint256 dyFee) {
        uint256[] memory multipliers = self.tokenPrecisionMultipliers;
        uint256[] memory xp = _xp(balances, multipliers);
        require(
            tokenIndexFrom < xp.length && tokenIndexTo < xp.length,
            "Token index out of range"
        );
        uint256 x = dx.mul(multipliers[tokenIndexFrom]).add(xp[tokenIndexFrom]);
        uint256 y = getY(
            _getAPrecise(self),
            tokenIndexFrom,
            tokenIndexTo,
            x,
            xp
        );
        dy = xp[tokenIndexTo].sub(y).sub(1);
        dyFee = dy.mul(self.swapFee).div(FEE_DENOMINATOR);
        dy = dy.sub(dyFee).div(multipliers[tokenIndexTo]);
    }


/**
 * @notice A simple method to calculate prices from deposits or
 * withdrawals, excluding fees but including slippage. This is
 * helpful as an input into the various "min" parameters on calls
 * to fight front-running
 *
 * @dev This shouldn't be used outside frontends for user estimates.
 *
 * @param self Swap struct to read from
 * @param account address of the account depositing or withdrawing tokens
 * @param amounts an array of token amounts to deposit or withdrawal,
 * corresponding to pooledTokens. The amount should be in each
 * pooled token's native precision. If a token charges a fee on transfers,
 * use the amount that gets transferred after the fee.
 * @param deposit whether this is a deposit or a withdrawal
 * @return if deposit was true, total amount of lp token that will be minted and if
 * deposit was false, total amount of lp token that will be burned
 */

function calculateTokenAmount(
        Swap storage self,
        address account,
        uint256[] calldata amounts,
        bool deposit
    ) external view returns (uint256) {
        uint256 a = _getAPrecise(self);
        uint256[] memory balances = self.balances;
        uint256[] memory multipliers = self.tokenPrecisionMultipliers;

        uint256 d0 = getD(_xp(balances, multipliers), a);
        for (uint256 i = 0; i < balances.length; i++) {
            if (deposit) {
                balances[i] = balances[i].add(amounts[i]);
            } else {
                balances[i] = balances[i].sub(
                    amounts[i],
                    "Cannot withdraw more than available"
                );
            }
        }
        uint256 d1 = getD(_xp(balances, multipliers), a);
        uint256 totalSupply = self.lpToken.totalSupply();

        if (deposit) {
            return d1.sub(d0).mul(totalSupply).div(d0);
        } else {
            return
                d0.sub(d1).mul(totalSupply).div(d0).mul(FEE_DENOMINATOR).div(
                    FEE_DENOMINATOR.sub(
                        _calculateCurrentWithdrawFee(self, account)
                    )
                );
        }
    }


/**
 * @notice swap two tokens in the pool
 * @param self Swap struct to read from and write to
 * @param tokenIndexFrom the token the user wants to sell
 * @param tokenIndexTo the token the user wants to buy
 * @param dx the amount of tokens the user wants to sell
 * @param minDy the min amount the user would like to receive, or revert.
 * @return amount of token user received on swap
 */

function swap(
        Swap storage self,
        uint8 tokenIndexFrom,
        uint8 tokenIndexTo,
        uint256 dx,
        uint256 minDy
    ) external returns (uint256) {
        {
            IERC20 tokenFrom = self.pooledTokens[tokenIndexFrom];
            require(
                dx <= tokenFrom.balanceOf(msg.sender),
                "Cannot swap more than you own"
            );
            // Transfer tokens first to see if a fee was charged on transfer
            uint256 beforeBalance = tokenFrom.balanceOf(address(this));
            tokenFrom.safeTransferFrom(msg.sender, address(this), dx);

            // Use the actual transferred amount for AMM math
            dx = tokenFrom.balanceOf(address(this)).sub(beforeBalance);
        }

        uint256 dy;
        uint256 dyFee;
        uint256[] memory balances = self.balances;
        (dy, dyFee) = _calculateSwap(
            self,
            tokenIndexFrom,
            tokenIndexTo,
            dx,
            balances
        );
        require(dy >= minDy, "Swap didn't result in min tokens");

        uint256 dyAdminFee = dyFee.mul(self.adminFee).div(FEE_DENOMINATOR).div(
            self.tokenPrecisionMultipliers[tokenIndexTo]
        );

        self.balances[tokenIndexFrom] = balances[tokenIndexFrom].add(dx);
        self.balances[tokenIndexTo] = balances[tokenIndexTo].sub(dy).sub(
            dyAdminFee
        );

        self.pooledTokens[tokenIndexTo].safeTransfer(msg.sender, dy);

        emit TokenSwap(msg.sender, dx, dy, tokenIndexFrom, tokenIndexTo);

        return dy;
    }


/**
 * @notice Update the withdraw fee for `user`. If the user is currently
 * not providing liquidity in the pool, sets to default value. If not, recalculate
 * the starting withdraw fee based on the last deposit's time & amount relative
 * to the new deposit.
 *
 * @param self Swap struct to read from and write to
 * @param user address of the user depositing tokens
 * @param toMint amount of pool tokens to be minted
 */

function updateUserWithdrawFee(
        Swap storage self,
        address user,
        uint256 toMint
    ) public {
        // If token is transferred to address 0 (or burned), don't update the fee.
        if (user == address(0)) {
            return;
        }
        if (self.defaultWithdrawFee == 0) {
            // If current fee is set to 0%, set multiplier to FEE_DENOMINATOR
            self.withdrawFeeMultiplier[user] = FEE_DENOMINATOR;
        } else {
            // Otherwise, calculate appropriate discount based on last deposit amount
            uint256 currentFee = _calculateCurrentWithdrawFee(self, user);
            uint256 currentBalance = self.lpToken.balanceOf(user);

            // ((currentBalance * currentFee) + (toMint * defaultWithdrawFee)) * FEE_DENOMINATOR /
            // ((toMint + currentBalance) * defaultWithdrawFee)
            self.withdrawFeeMultiplier[user] = currentBalance
                .mul(currentFee)
                .add(toMint.mul(self.defaultWithdrawFee))
                .mul(FEE_DENOMINATOR)
                .div(toMint.add(currentBalance).mul(self.defaultWithdrawFee));
        }
        self.depositTimestamp[user] = block.timestamp;
    }


/**
 * @notice Burn LP tokens to remove liquidity from the pool.
 * @dev Liquidity can always be removed, even when the pool is paused.
 * @param self Swap struct to read from and write to
 * @param amount the amount of LP tokens to burn
 * @param minAmounts the minimum amounts of each token in the pool
 * acceptable for this burn. Useful as a front-running mitigation
 * @return amounts of tokens the user received
 */

function removeLiquidity(
        Swap storage self,
        uint256 amount,
        uint256[] calldata minAmounts
    ) external returns (uint256[] memory) {
        LPToken lpToken = self.lpToken;
        IERC20[] memory pooledTokens = self.pooledTokens;
        require(amount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf");
        require(
            minAmounts.length == pooledTokens.length,
            "minAmounts must match poolTokens"
        );

        uint256[] memory balances = self.balances;
        uint256 totalSupply = lpToken.totalSupply();

        uint256[] memory amounts = _calculateRemoveLiquidity(
            self,
            balances,
            msg.sender,
            amount,
            totalSupply
        );

        for (uint256 i = 0; i < amounts.length; i++) {
            require(amounts[i] >= minAmounts[i], "amounts[i] < minAmounts[i]");
            self.balances[i] = balances[i].sub(amounts[i]);
            pooledTokens[i].safeTransfer(msg.sender, amounts[i]);
        }

        lpToken.burnFrom(msg.sender, amount);

        emit RemoveLiquidity(msg.sender, amounts, totalSupply.sub(amount));

        return amounts;
    }


/**
 * @notice Remove liquidity from the pool all in one token.
 * @param self Swap struct to read from and write to
 * @param tokenAmount the amount of the lp tokens to burn
 * @param tokenIndex the index of the token you want to receive
 * @param minAmount the minimum amount to withdraw, otherwise revert
 * @return amount chosen token that user received
 */

function removeLiquidityOneToken(
        Swap storage self,
        uint256 tokenAmount,
        uint8 tokenIndex,
        uint256 minAmount
    ) external returns (uint256) {
        LPToken lpToken = self.lpToken;
        IERC20[] memory pooledTokens = self.pooledTokens;

        require(tokenAmount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf");
        require(tokenIndex < pooledTokens.length, "Token not found");

        uint256 totalSupply = lpToken.totalSupply();

        (uint256 dy, uint256 dyFee) = _calculateWithdrawOneToken(
            self,
            msg.sender,
            tokenAmount,
            tokenIndex,
            totalSupply
        );

        require(dy >= minAmount, "dy < minAmount");

        self.balances[tokenIndex] = self.balances[tokenIndex].sub(
            dy.add(dyFee.mul(self.adminFee).div(FEE_DENOMINATOR))
        );
        lpToken.burnFrom(msg.sender, tokenAmount);
        pooledTokens[tokenIndex].safeTransfer(msg.sender, dy);

        emit RemoveLiquidityOne(
            msg.sender,
            tokenAmount,
            totalSupply,
            tokenIndex,
            dy
        );

        return dy;
    }


/**
 * @notice withdraw all admin fees to a given address
 * @param self Swap struct to withdraw fees from
 * @param to Address to send the fees to
 */

function withdrawAdminFees(Swap storage self, address to) external {
        IERC20[] memory pooledTokens = self.pooledTokens;
        for (uint256 i = 0; i < pooledTokens.length; i++) {
            IERC20 token = pooledTokens[i];
            uint256 balance = token.balanceOf(address(this)).sub(
                self.balances[i]
            );
            if (balance != 0) {
                token.safeTransfer(to, balance);
            }
        }
    }


/**
 * @dev Change the name of the asteroid
 */

function setName(uint _asteroidId, string memory _newName) external whenNotPaused {
    require(_msgSender() == token.ownerOf(_asteroidId), "ERC721: caller is not the owner");
    require(validateName(_newName) == true, "Invalid name");
    require(isNameUsed(_newName) == false, "Name already in use");

    // If already named, dereserve old name
    if (bytes(_asteroidNames[_asteroidId]).length > 0) {
      toggleNameUsed(_asteroidNames[_asteroidId], false);
    }

    toggleNameUsed(_newName, true);
    _asteroidNames[_asteroidId] = _newName;
    emit NameChanged(_asteroidId, _newName);
  }


/**
 * @dev Check if the name string is valid
 * Between 1 and 32 characters (Alphanumeric and spaces without leading or trailing space)
 */

function validateName(string memory str) public pure returns (bool){
    bytes memory b = bytes(str);

    if(b.length < 1) return false;
    if(b.length > 32) return false; // Cannot be longer than 25 characters
    if(b[0] == 0x20) return false; // Leading space
    if (b[b.length - 1] == 0x20) return false; // Trailing space

    bytes1 lastChar = b[0];

    for (uint i; i < b.length; i++) {
      bytes1 char = b[i];

      if (char == 0x20 && lastChar == 0x20) return false; // Cannot contain continous spaces

      if (
        !(char >= 0x30 && char <= 0x39) && //9-0
        !(char >= 0x41 && char <= 0x5A) && //A-Z
        !(char >= 0x61 && char <= 0x7A) && //a-z
        !(char == 0x20) //space
      ) {
        return false;
      }

      lastChar = char;
    }

    return true;
  }


//@dev Prior approval by the "addressHoldingNFTs" of this deployed contract
// to the Timeless contract is required for all IDs that should be allowed to be exchanged

function exchangeTimeless(uint256 tokenId) external payable {
        require(tokenIdToAddress[tokenId] == msg.sender, "You are not allowed to buy one!");
        require(claimed[tokenId] == false, "TokenID already claimed!");
        require(msg.value == 0.222 ether, "Please send exactly 0.222 ETH");

        IERC721(timelessAddr).safeTransferFrom(addressHoldingNFTs, msg.sender, tokenId);

        claimed[tokenId] = true;
        leftUnclaimed--;
    }


/**
 * @notice create and deploy multiple vest contracts.
 * @notice can be called by onlyOwner.
 * @param startTime vesting startTime in EPOCH.
 * @param endTime vesting endTime in EPOCH.
 * @param cliff duration when claim starts.
 * @param unlockDuration every liner unlocking schedule in seconds.
 * @param allowReleaseAll allow release All once.
 * @return vestAddress A vesting address.
 */

function createVest(
        uint256 startTime,
        uint256 endTime,
        uint256 cliff,
        uint256 unlockDuration,
        bool allowReleaseAll
    ) external onlyOwner returns (address vestAddress) {
        vestAddress = address(
            new UnifarmVesting(
                _msgSender(),
                startTime.add(cliff),
                startTime,
                startTime.add(endTime),
                unlockDuration,
                allowReleaseAll,
                address(this)
            )
        );

        vested.push(vestAddress);
        emit Vested(vestAddress, block.timestamp);
    }


/**
 * Get base asset info
 */

function getAssetBaseInfo() public view onlyValid
        returns (
            uint _id,
            uint _assetPrice,
            bool _isTradable,
            string _remark1,
            string _remark2
        )
    {
        _id = id;
        _assetPrice = assetPrice;
        _isTradable = isTradable;

        _remark1 = remark1;
        _remark2 = remark2;
    }


/**
 * overwrite the transferOwnership interface in ownable.
 * Only can transfer when the token is not splitted into small keys.
 * After transfer, the token should be set in "no trading" status.
 */

function transferOwnership(address newowner) public onlyOwner onlyValid onlyUnsplitted {
        owner = newowner;
        isTradable = false;  // set to false for new owner

        emit TokenUpdateEvent (
            id,
            isValid,
            isTradable,
            owner,
            assetPrice,
            assetFile.link,
            legalFile.link
        );
    }


/**
 * Buy asset
 */

function buy() public payable onlyValid onlyUnsplitted {
        require(isTradable == true, "contract is tradeable");
        require(msg.value >= assetPrice, "assetPrice not match");
        address origin_owner = owner;

        owner = msg.sender;
        isTradable = false;  // set to false for new owner

        emit TokenUpdateEvent (
            id,
            isValid,
            isTradable,
            owner,
            assetPrice,
            assetFile.link,
            legalFile.link
        );

        uint priviousBalance = pendingWithdrawals[origin_owner];
        pendingWithdrawals[origin_owner] = priviousBalance.add(assetPrice);
    }


/**
 *
 * Warning: may fail when number of owners exceeds 100 due to gas limit of a block in Ethereum.
 */

function distributeDivident(uint amount) public {
        // stableToken.approve(address(this), amount)
        // should be called by the caller to the token contract in previous
        uint value = 0;
        uint length = allowners.length;
        require(stableToken.balanceOf(msg.sender) >= amount, "Insufficient balance for sender");
        require(stableToken.allowance(msg.sender, address(this)) >= amount, "Insufficient allowance for contract");
        for (uint i = 0; i < length; i++) {
            //value = amount * balances[allowners[i]] / _totalSupply;
            value = amount.mul(balances[allowners[i]]);
            value = value.div(_totalSupply);

            // Always use a require when doing token transfer!
            // Do not think it works like the transfer method for ether,
            // which handles failure and will throw for you.
            require(stableToken.transferFrom(msg.sender, allowners[i], value));
        }
    }


/**
 * key inssurance. Split the whole token into small keys.
 * Only the owner can perform this when the token is still valid and unsplitted.
 * @param _supply Totol supply in ERC20 standard
 * @param _decim  Decimal parameter in ERC20 standard
 * @param _price The force acquisition price. If a claimer is willing to pay more than this value, he can
 * buy the keys forcibly. Notice: the claimer can only buy all keys at one time or buy nothing and the
 * buying process is delegated into a trusted agent. i.e. the operator.
 * @param _address The initial distribution plan for the keys. This parameter contains the addresses.
 * @param _amount  The amount corresponding to the initial distribution addresses.
 */

function split(uint _supply, uint8 _decim, uint _price, address[] _address, uint[] _amount)
        public
        onlyOwner
        onlyValid
        onlyUnsplitted
    {
        require(_address.length == _amount.length);

        isSplitted = true;
        _totalSupply = _supply * 10 ** uint(_decim);
        decimals = _decim;
        collectPrice = _price;

        uint amount = 0;
        uint length = _address.length;

        balances[msg.sender] = _totalSupply;
        if (indexOfowner[msg.sender] == 0) {
            allowners.push(msg.sender);
            indexOfowner[msg.sender] = allowners.length;
        }
        emit Transfer(address(0), msg.sender, _totalSupply);

        for (uint i = 0; i < length; i++) {
            amount = _amount[i]; // * 10 ** uint(_decim);
            balances[_address[i]] = amount;
            balances[msg.sender] = balances[msg.sender].sub(amount);

            // ensure that each address appears only once in allowners list
            // so that distribute divident or force collect only pays one time
            if (indexOfowner[_address[i]] == 0) {
                allowners.push(_address[i]);
                indexOfowner[_address[i]] = allowners.length;
            }
            emit Transfer(msg.sender, _address[i], amount);
        }

        emit TokenSplitEvent(id, _supply, _decim, _price);
    }


/**
 * Token conversion. Turn the keys to a whole token.
 * Only the sender with all keys in hand can perform this and he will be the new owner.
 */

function merge() public onlyValid onlySplitted {
        require(balances[msg.sender] == _totalSupply);
        _totalSupply = 0;
        balances[msg.sender] = 0;
        owner = msg.sender;
        isTradable = false;
        isSplitted = false;
        emit Transfer(msg.sender, address(0), _totalSupply);
        emit TokenMergeEvent(id, msg.sender);
    }


/**
 * @notice Calculates and returns A based on the ramp settings
 * @dev See the StableSwap paper for details
 * @param self Swap struct to read from
 * @return A parameter in its raw precision form
 */

function _getAPrecise(Swap storage self) internal view returns (uint256) {
        uint256 t1 = self.futureATime; // time when ramp is finished
        uint256 a1 = self.futureA; // final A value when ramp is finished

        if (block.timestamp < t1) {
            uint256 t0 = self.initialATime; // time when ramp is started
            uint256 a0 = self.initialA; // initial A value when ramp is started
            if (a1 > a0) {
                // a0 + (a1 - a0) * (block.timestamp - t0) / (t1 - t0)
                return
                    a0.add(
                        a1.sub(a0).mul(block.timestamp.sub(t0)).div(t1.sub(t0))
                    );
            } else {
                // a0 - (a0 - a1) * (block.timestamp - t0) / (t1 - t0)
                return
                    a0.sub(
                        a0.sub(a1).mul(block.timestamp.sub(t0)).div(t1.sub(t0))
                    );
            }
        } else {
            return a1;
        }
    }


/**
 * @notice Get the ETHUSD price from ChainLink and convert to a mantissa value
 * @return USD price mantissa
 */

function getETHUSDCPrice() public view returns (uint256) {
        (
            uint80 roundID,
            int256 price,
            uint256 startedAt,
            uint256 timeStamp,
            uint80 answeredInRound
        ) = priceFeedETHUSD.latestRoundData();
        // Get decimals of price feed
        uint256 decimals = priceFeedETHUSD.decimals();
        // Add decimal places to format an 18 decimal mantissa
        uint256 priceMantissa =
            uint256(price).mul(10**(mantissaDecimals.sub(decimals)));

        return priceMantissa;
    }


/**
 * @notice Calculate the fee that is applied when the given user withdraws.
 * Withdraw fee decays linearly over 4 weeks.
 * @param user address you want to calculate withdraw fee of
 * @return current withdraw fee of the user
 */

function calculateCurrentWithdrawFee(Swap storage self, address user)
        public
        view
        returns (uint256)
    {
        uint256 endTime = self.depositTimestamp[user].add(4 weeks);
        if (endTime > block.timestamp) {
            uint256 timeLeftover = endTime.sub(block.timestamp);
            return
                self
                    .defaultWithdrawFee
                    .mul(self.withdrawFeeMultiplier[user])
                    .mul(timeLeftover)
                    .div(4 weeks)
                    .div(FEE_DENOMINATOR);
        }
        return 0;
    }


// mints new DSProxies and stores them to the proxy array

function mint(uint256 _value) public {
        require(_value % 1e18 == 0);

        uint newTokens = _value / 1e18;

        for (uint256 i = 0; i < newTokens; i++) {
            address proxy = address(makeChild());
            _proxies.push(proxy);
        }
        _mint(_msgSender(), _value);
    }


/*
 * createLGEWhitelist - Call this after initial Token Generation Event (TGE)
 *
 * pairAddress - address generated from createPair() event on DEX
 * durations - array of durations (seconds) for each whitelist rounds
 * amountsMax - array of max amounts (TOKEN decimals) for each whitelist round
 *
 */

function createLGEWhitelist(address pairAddress, uint256[] calldata durations, uint256[] calldata amountsMax) external onlyWhitelister() {
        require(durations.length == amountsMax.length, "Invalid whitelist(s)");

        _lgePairAddress = pairAddress;

        if(durations.length > 0) {

            delete _lgeWhitelistRounds;

            for (uint256 i = 0; i < durations.length; i++) {
                _lgeWhitelistRounds.push(WhitelistRound(durations[i], amountsMax[i]));
            }

        }
    }


/*
 * modifyLGEWhitelistAddresses - Define what addresses are included/excluded from a whitelist round
 *
 * index - 0-based index of round to modify whitelist
 * duration - period in seconds from LGE event or previous whitelist round
 * amountMax - max amount (TOKEN decimals) for each whitelist round
 *
 */

function modifyLGEWhitelist(uint256 index, uint256 duration, uint256 amountMax, address[] calldata addresses, bool enabled) external onlyWhitelister() {
        require(index < _lgeWhitelistRounds.length, "Invalid index");
        require(amountMax > 0, "Invalid amountMax");

        if(duration != _lgeWhitelistRounds[index].duration)
            _lgeWhitelistRounds[index].duration = duration;

        if(amountMax != _lgeWhitelistRounds[index].amountMax)
            _lgeWhitelistRounds[index].amountMax = amountMax;

        for (uint256 i = 0; i < addresses.length; i++) {
            _lgeWhitelistRounds[index].addresses[addresses[i]] = enabled;
        }
    }