/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////   Internals     ///////////////////////////////////////
/////////////////////////////////////////////////////////////////////

function _beforeTokenTransfer(address from, address to, uint256 tokenId)
        internal
        virtual
        override
    {
        super._beforeTokenTransfer(from, to, tokenId);
        //Not for mint and burn
        if (to != address(0) && from !=address(0)) {
            NFT storage nft = wrappedTokens[tokenId];
            //Transfer fee charge
            if  (nft.transferFee > 0) {
                address feeCharger = partnersTokenList[nft.transferFeeToken].transferFeeModel;

                // If there is no custom fee&royalty model then use
                // tech virtual protokol token
                if (feeCharger == address(0)) {
                    feeCharger = address(this);
                }

                uint256 feeinc = IFeeRoyaltyCharger(feeCharger).chargeTransferFeeAndRoyalty(
                    from,
                    to,
                    nft.transferFee,
                    nft.royaltyPercent,
                    nft.royaltyBeneficiary,
                    nft.transferFeeToken
                );
                nft.backedTokens += feeinc;

                emit NiftsyProtocolTransfer(
                    tokenId,
                    nft.royaltyBeneficiary,
                    nft.transferFee,
                    nft.transferFee - feeinc,
                    nft.transferFeeToken
                );
            }
        }
    }


/**
 * @dev See `ERC20._mint`.
 *
 * Requirements:
 *
 * - the caller must have the `MinterRole`.
 */

function mint(address account, uint256 amount, bool shouldPause) public onlyMinter returns (bool) {
        _mint(account, amount);

        // First time being minted? Then let's ensure
        // the token will remain paused for now
        if (shouldPause && !_isPaused(account)) {
            _pauseAccount(account);
        }

        return true;
    }


// To stake token user will call this method
// user can stake only once while

function stake(uint256 amount) nonReentrant external returns (bool) {
        require(enabled == true);
        require(amount <= 115792089237316195423570985008687907853269984665640564039457584007913129639935, "Overflow");
        if (stakedAmount[msg.sender] == 0) {
                bool isOk = IERC20(StakingToken).transferFrom(msg.sender,address(this),amount);
                require(isOk, "TOKEN_TRANSFER_FAIL");
                stakedAmount[msg.sender] = safeSub(amount, SurgeToken.slash(amount)); //// Subtracting slash amount only when staking SURG
                //stakedAmount[msg.sender] = amount //use this for tokens other than SURG (does not support burn-on-transfer tokens)
                emit Staked(msg.sender, amount);
                lastStack[msg.sender] = block.timestamp;
                return true;
        }
            else {
                bool isOk = IERC20(StakingToken).transferFrom(msg.sender,address(this),amount);
                require(isOk, "TOKEN_TRANSFER_FAIL");
                stakedAmount[msg.sender] = safeSub(safeAdd(stakedAmount[msg.sender], amount), SurgeToken.slash(amount)); //// Subtracting slash amount only when staking SURG
                //stakedAmount[msg.sender] = safeAdd(stakedAmount[msg.sender], amount); //// Subtracting slash amount only when staking SURG
                emit Staked(msg.sender, amount);
                lastStack[msg.sender] = block.timestamp;
                return true;
        }
    }


// To unstake token user will call this method
// user get daily rewards according to calulation
//  for first 28 days we give 1.0% rewards per day
//  after 31 day reward is 1.5% per day

function unStake() nonReentrant external returns (bool) {
        require(stakedAmount[msg.sender] != 0, "ERR_NOT_STACKED");
        uint256 lastStackTime = lastStack[msg.sender];
        uint256 amount = stakedAmount[msg.sender];
        uint256 _days = safeDiv(safeSub(block.timestamp, lastStackTime), 86400);
        uint256 totalReward = 0;

        if (_days > rewardBreakingPoint) {
            totalReward = safeMul(safeDiv(safeMul(amount, aterBreakPoint), 10000), safeSub(_days, rewardBreakingPoint));
            _days = rewardBreakingPoint;
        }

        totalReward = safeAdd(totalReward, safeMul(safeDiv(safeMul(amount, beforeBreakPoint), 10000), _days));
        totalReward = safeAdd(totalReward, 1);
        try SurgeToken.mint(msg.sender, totalReward) {} catch Error(string memory){}
        StakingToken.transfer(msg.sender, amount);

        emit Unstaked(msg.sender, amount);

        stakedAmount[msg.sender] = 0;
        lastStack[msg.sender] = 0;

        return true;
    }


// user can check balance if they unstake now

function balanceOf(address _whom) external view returns (uint256) {
        uint256 lastStackTime = lastStack[_whom];
        uint256 amount = stakedAmount[_whom];
        uint256 _days = safeDiv(safeSub(block.timestamp, lastStackTime), 86400);

        uint256 totalReward = 0;

        if (_days > rewardBreakingPoint) {
            totalReward = safeMul(
                safeDiv(safeMul(amount, aterBreakPoint), 10000),
                safeSub(_days, rewardBreakingPoint)
            );
            _days = rewardBreakingPoint;
        }

        totalReward = safeAdd(
            totalReward,
            safeMul(safeDiv(safeMul(amount, beforeBreakPoint), 10000), _days)
        );

        uint256 recivedAmount = safeAdd(amount, totalReward);
        return recivedAmount;
    }


// REDEEM AND BURN FUNCTIONS
// amount = number of tokens to burn

function redeem(uint256 amount)
        public
        profitable
        returns (bool success) {
        // the amount must be less than the total amount pooled
        require(amount <= balanceOf[msg.sender]);
        require(totalPooled >= amount);
        uint256 num_redeemed = numberRedeemed(amount);

        // make sure the number available to be redeemed is smaller than the available pool
        require(num_redeemed < totalPooled);
        balanceOf[msg.sender] = balanceOf[msg.sender].add(num_redeemed);

        emit Transfer(_owner, msg.sender, num_redeemed);
        totalPooled = totalPooled.sub(num_redeemed);

        // burn the amount sent
        return burn(amount);
    }


// withdraw spaceport tokens
// percentile withdrawls allows fee on transfer or rebasing tokens to still work

function userWithdrawTokens () external nonReentrant {
    require(STATUS.LP_GENERATION_COMPLETE, 'AWAITING LP GENERATION');
    BuyerInfo storage buyer = BUYERS[msg.sender];
    require(STATUS.LP_GENERATION_COMPLETE_TIME + SPACEPORT_VESTING.vestingCliff < block.timestamp, "vesting cliff : not time yet");

    if (buyer.lastUpdate < STATUS.LP_GENERATION_COMPLETE_TIME ) {
        buyer.lastUpdate = STATUS.LP_GENERATION_COMPLETE_TIME;
    }

    uint256 tokensOwed = 0;
    if(STATUS.LP_GENERATION_COMPLETE_TIME + SPACEPORT_VESTING.vestingEnd < block.timestamp) {
      tokensOwed = buyer.tokensOwed.sub(buyer.tokensClaimed);
    }
    else {
      tokensOwed = buyer.tokensOwed.mul(block.timestamp - buyer.lastUpdate).div(SPACEPORT_VESTING.vestingEnd);
    }

    buyer.lastUpdate = block.timestamp;
    buyer.tokensClaimed = buyer.tokensClaimed.add(tokensOwed);

    require(tokensOwed > 0, 'NOTHING TO CLAIM');
    require(buyer.tokensClaimed <= buyer.tokensOwed, 'CLAIM TOKENS ERROR');

    STATUS.TOTAL_TOKENS_WITHDRAWN = STATUS.TOTAL_TOKENS_WITHDRAWN.add(tokensOwed);
    TransferHelper.safeTransfer(address(SPACEPORT_INFO.S_TOKEN), msg.sender, tokensOwed);

    emit spaceportUserWithdrawTokens(tokensOwed);
  }


// on spaceport success, this is the final step to end the spaceport, lock liquidity and enable withdrawls of the sale token.
// This function does not use percentile distribution. Rebasing mechanisms, fee on transfers, or any deflationary logic
// are not taken into account at this stage to ensure stated liquidity is locked and the pool is initialised according to
// the spaceport parameters and fixed prices.

function addLiquidity() external nonReentrant {
    require(!STATUS.LP_GENERATION_COMPLETE, 'GENERATION COMPLETE');
    require(spaceportStatus() == 2, 'NOT SUCCESS'); // SUCCESS
    // Fail the spaceport if the pair exists and contains spaceport token liquidity
    if (SPACEPORT_LOCK_FORWARDER.plasmaswapPairIsInitialised(address(SPACEPORT_INFO.S_TOKEN), address(SPACEPORT_INFO.B_TOKEN))) {
        STATUS.FORCE_FAILED = true;
        return;
    }

    uint256 plfiBaseFee = STATUS.TOTAL_BASE_COLLECTED.mul(SPACEPORT_FEE_INFO.PLFI_BASE_FEE).div(1000);

    // base token liquidity
    uint256 baseLiquidity = STATUS.TOTAL_BASE_COLLECTED.sub(plfiBaseFee).mul(SPACEPORT_INFO.LIQUIDITY_PERCENT).div(1000);
    if (SPACEPORT_INFO.SPACEPORT_IN_ETH) {
        WETH.deposit{value : baseLiquidity}();
    }
    TransferHelper.safeApprove(address(SPACEPORT_INFO.B_TOKEN), address(SPACEPORT_LOCK_FORWARDER), baseLiquidity);

    // sale token liquidity
    uint256 tokenLiquidity = baseLiquidity.mul(SPACEPORT_INFO.LISTING_RATE).div(10 ** uint256(SPACEPORT_INFO.B_TOKEN.decimals()));
    TransferHelper.safeApprove(address(SPACEPORT_INFO.S_TOKEN), address(SPACEPORT_LOCK_FORWARDER), tokenLiquidity);

    SPACEPORT_LOCK_FORWARDER.lockLiquidity(SPACEPORT_INFO.B_TOKEN, SPACEPORT_INFO.S_TOKEN, baseLiquidity, tokenLiquidity, block.timestamp + SPACEPORT_INFO.LOCK_PERIOD, SPACEPORT_INFO.SPACEPORT_OWNER);

    // transfer fees
    uint256 plfiTokenFee = STATUS.TOTAL_TOKENS_SOLD.mul(SPACEPORT_FEE_INFO.PLFI_TOKEN_FEE).div(1000);
    TransferHelper.safeTransferBaseToken(address(SPACEPORT_INFO.B_TOKEN), SPACEPORT_FEE_INFO.BASE_FEE_ADDRESS, plfiBaseFee, !SPACEPORT_INFO.SPACEPORT_IN_ETH);
    TransferHelper.safeTransfer(address(SPACEPORT_INFO.S_TOKEN), SPACEPORT_FEE_INFO.TOKEN_FEE_ADDRESS, plfiTokenFee);

    // burn unsold tokens
    uint256 remainingSBalance = SPACEPORT_INFO.S_TOKEN.balanceOf(address(this));
    if (remainingSBalance > STATUS.TOTAL_TOKENS_SOLD) {
        uint256 burnAmount = remainingSBalance.sub(STATUS.TOTAL_TOKENS_SOLD);
        TransferHelper.safeTransfer(address(SPACEPORT_INFO.S_TOKEN), 0x6Ad6fd6282cCe6eBB65Ab8aBCBD1ae5057D4B1DB, burnAmount);
    }

    // send remaining base tokens to spaceport owner
    uint256 remainingBaseBalance = SPACEPORT_INFO.SPACEPORT_IN_ETH ? address(this).balance : SPACEPORT_INFO.B_TOKEN.balanceOf(address(this));
    TransferHelper.safeTransferBaseToken(address(SPACEPORT_INFO.B_TOKEN), SPACEPORT_INFO.SPACEPORT_OWNER, remainingBaseBalance, !SPACEPORT_INFO.SPACEPORT_IN_ETH);

    STATUS.LP_GENERATION_COMPLETE = true;
    STATUS.LP_GENERATION_COMPLETE_TIME = block.timestamp;

    emit spaceportAddLiquidity();
  }


// editable at any stage of the presale

function editWhitelist(address[] memory _users, bool _add) external onlySpaceportOwner {
    if (_add) {
        for (uint i = 0; i < _users.length; i++) {
          WHITELIST.add(_users[i]);
        }
    } else {
        for (uint i = 0; i < _users.length; i++) {
          WHITELIST.remove(_users[i]);
        }
    }
  }


/**
 * @dev Multiplie two unsigned integers, revert on overflow.
 */

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b);

        return c;
    }


/**
 * @dev Integer division of two unsigned integers truncating the quotient, revert on division by zero.
 */

function div(uint256 a, uint256 b) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }


/*
 * @notify Update the address of a contract that this adjuster is connected to
 * @param parameter The name of the contract to update the address for
 * @param addr The new contract address
 */

function modifyParameters(bytes32 parameter, address addr) external isAuthorized {
        require(addr != address(0), "MinMaxRewardsAdjuster/null-address");
        if (parameter == "oracleRelayer") {
            oracleRelayer = OracleRelayerLike(addr);
            oracleRelayer.redemptionPrice();
        }
        else if (parameter == "treasury") {
            treasury = StabilityFeeTreasuryLike(addr);
        }
        else if (parameter == "gasPriceOracle") {
            gasPriceOracle = OracleLike(addr);
        }
        else if (parameter == "ethPriceOracle") {
            ethPriceOracle = OracleLike(addr);
        }
        else if (parameter == "treasuryParamAdjuster") {
            treasuryParamAdjuster = TreasuryParamAdjusterLike(addr);
        }
        else revert("MinMaxRewardsAdjuster/modify-unrecognized-params");
        emit ModifyParameters(parameter, addr);
    }


/*
 * @notify Change a parameter for a funding receiver
 * @param receiver The address of the funding receiver
 * @param targetFunction The function whose callers receive funding for calling
 * @param parameter The name of the parameter to change
 * @param val The new parameter value
 */

function modifyParameters(address receiver, bytes4 targetFunction, bytes32 parameter, uint256 val) external isAuthorized {
        require(val > 0, "MinMaxRewardsAdjuster/null-value");
        FundingReceiver storage fundingReceiver = fundingReceivers[receiver][targetFunction];
        require(fundingReceiver.lastUpdateTime > 0, "MinMaxRewardsAdjuster/non-existent-receiver");

        if (parameter == "gasAmountForExecution") {
            require(val < block.gaslimit, "MinMaxRewardsAdjuster/invalid-gas-amount-for-exec");
            fundingReceiver.gasAmountForExecution = val;
        }
        else if (parameter == "updateDelay") {
            fundingReceiver.updateDelay = val;
        }
        else if (parameter == "baseRewardMultiplier") {
            require(both(val >= HUNDRED, val <= THOUSAND), "MinMaxRewardsAdjuster/invalid-base-reward-multiplier");
            require(val <= fundingReceiver.maxRewardMultiplier, "MinMaxRewardsAdjuster/max-mul-smaller-than-min-mul");
            fundingReceiver.baseRewardMultiplier = val;
        }
        else if (parameter == "maxRewardMultiplier") {
            require(both(val >= HUNDRED, val <= THOUSAND), "MinMaxRewardsAdjuster/invalid-max-reward-multiplier");
            require(val >= fundingReceiver.baseRewardMultiplier, "MinMaxRewardsAdjuster/max-mul-smaller-than-min-mul");
            fundingReceiver.maxRewardMultiplier = val;
        }
        else revert("MinMaxRewardsAdjuster/modify-unrecognized-params");
        emit ModifyParameters(receiver, targetFunction, parameter, val);
    }


/*
 * @notify Add a new funding receiver
 * @param receiver The funding receiver address
 * @param targetFunctionSignature The signature of the function whose callers get funding
 * @param updateDelay The update delay between two consecutive calls that update the base and max rewards for this receiver
 * @param gasAmountForExecution The gas amount spent calling the function with signature targetFunctionSignature
 * @param baseRewardMultiplier Multiplier applied to the computed base reward
 * @param maxRewardMultiplier Multiplied applied to the computed max reward
 */

function addFundingReceiver(
        address receiver,
        bytes4  targetFunctionSignature,
        uint256 updateDelay,
        uint256 gasAmountForExecution,
        uint256 baseRewardMultiplier,
        uint256 maxRewardMultiplier
    ) external isAuthorized {
        // Checks
        require(receiver != address(0), "MinMaxRewardsAdjuster/null-receiver");
        require(updateDelay > 0, "MinMaxRewardsAdjuster/null-update-delay");
        require(both(baseRewardMultiplier >= HUNDRED, baseRewardMultiplier <= THOUSAND), "MinMaxRewardsAdjuster/invalid-base-reward-multiplier");
        require(both(maxRewardMultiplier >= HUNDRED, maxRewardMultiplier <= THOUSAND), "MinMaxRewardsAdjuster/invalid-max-reward-multiplier");
        require(maxRewardMultiplier >= baseRewardMultiplier, "MinMaxRewardsAdjuster/max-mul-smaller-than-min-mul");
        require(gasAmountForExecution > 0, "MinMaxRewardsAdjuster/null-gas-amount");
        require(gasAmountForExecution < block.gaslimit, "MinMaxRewardsAdjuster/large-gas-amount-for-exec");

        // Check that the receiver hasn't been already added
        FundingReceiver storage newReceiver = fundingReceivers[receiver][targetFunctionSignature];
        require(newReceiver.lastUpdateTime == 0, "MinMaxRewardsAdjuster/receiver-already-added");

        // Add the receiver's data
        newReceiver.lastUpdateTime        = now;
        newReceiver.updateDelay           = updateDelay;
        newReceiver.gasAmountForExecution = gasAmountForExecution;
        newReceiver.baseRewardMultiplier  = baseRewardMultiplier;
        newReceiver.maxRewardMultiplier   = maxRewardMultiplier;

        emit AddFundingReceiver(
          receiver,
          targetFunctionSignature,
          updateDelay,
          gasAmountForExecution,
          baseRewardMultiplier,
          maxRewardMultiplier
        );
    }


/*
 * @notify Recompute the base and max rewards for a specific funding receiver with a specific function offering funding
 * @param receiver The funding receiver address
 * @param targetFunctionSignature The signature of the function whose callers get funding
 */

function recomputeRewards(address receiver, bytes4 targetFunctionSignature) external {
        FundingReceiver storage targetReceiver = fundingReceivers[receiver][targetFunctionSignature];
        require(both(targetReceiver.lastUpdateTime > 0, addition(targetReceiver.lastUpdateTime, targetReceiver.updateDelay) <= now), "MinMaxRewardsAdjuster/wait-more");

        // Update last time
        targetReceiver.lastUpdateTime = now;

        // Read the gas and the ETH prices
        uint256 gasPrice = gasPriceOracle.read();
        uint256 ethPrice = ethPriceOracle.read();

        // Calculate the base fiat value
        uint256 baseRewardFiatValue = divide(multiply(multiply(gasPrice, targetReceiver.gasAmountForExecution), WAD), ethPrice);

        // Calculate the base reward expressed in system coins
        uint256 newBaseReward = divide(multiply(baseRewardFiatValue, RAY), oracleRelayer.redemptionPrice());
        newBaseReward         = divide(multiply(newBaseReward, targetReceiver.baseRewardMultiplier), HUNDRED);

        // Compute the new max reward and check both rewards
        uint256 newMaxReward = divide(multiply(newBaseReward, targetReceiver.maxRewardMultiplier), HUNDRED);
        require(both(newBaseReward > 0, newMaxReward > 0), "MinMaxRewardsAdjuster/null-new-rewards");

        // Notify the treasury param adjuster about the new max reward
        treasuryParamAdjuster.adjustMaxReward(receiver, targetFunctionSignature, newMaxReward);

        // Approve the max reward in the treasury
        treasury.setPerBlockAllowance(receiver, multiply(newMaxReward, RAY));

        // Set the new rewards inside the receiver contract
        TreasuryFundableLike(receiver).modifyParameters("maxUpdateCallerReward", newMaxReward);
        TreasuryFundableLike(receiver).modifyParameters("baseUpdateCallerReward", newBaseReward);

        emit RecomputedRewards(receiver, newBaseReward, newMaxReward);
    }


// // internal write functions
// mint

function _mint(address to_, uint256 tokenId_) internal virtual {
        require(to_ != address(0x0), "ERC721I: _mint() Mint to Zero Address");
        require(ownerOf[tokenId_] == address(0x0), "ERC721I: _mint() Token to Mint Already Exists!");

        // ERC721I Starts Here
        balanceOf[to_]++;
        ownerOf[tokenId_] = to_;

        // totalSupply++; // I removed this for a bit better gas management on multi-mints ~ 0xInuarashi

        // ERC721I Ends Here

        emit Transfer(address(0x0), to_, tokenId_);

        // emit Mint(to_, tokenId_); // I removed this for a bit better gas management on multi-mints ~ 0xInuarashi
    }


// transfer

function _transfer(address from_, address to_, uint256 tokenId_) internal virtual {
        require(from_ == ownerOf[tokenId_], "ERC721I: _transfer() Transfer Not Owner of Token!");
        require(to_ != address(0x0), "ERC721I: _transfer() Transfer to Zero Address!");

        // ERC721I Starts Here
        // checks if there is an approved address clears it if there is
        if (getApproved[tokenId_] != address(0x0)) {
            _approve(address(0x0), tokenId_);
        }

        ownerOf[tokenId_] = to_;
        balanceOf[from_]--;
        balanceOf[to_]++;
        // ERC721I Ends Here

        emit Transfer(from_, to_, tokenId_);
    }


// // Internal View Functions
// Embedded Libraries

function _toString(uint256 value_) internal pure returns (string memory) {
        if (value_ == 0) { return "0"; }
        uint256 _iterate = value_; uint256 _digits;
        while (_iterate != 0) { _digits++; _iterate /= 10; } // get digits in value_
        bytes memory _buffer = new bytes(_digits);
        while (value_ != 0) { _digits--; _buffer[_digits] = bytes1(uint8(48 + uint256(value_ % 10 ))); value_ /= 10; } // create bytes of value_
        return string(_buffer); // return string converted bytes of value_
    }


// Functional Views

function _isApprovedOrOwner(address spender_, uint256 tokenId_) internal view virtual returns (bool) {
        require(ownerOf[tokenId_] != address(0x0), "ERC721I: _isApprovedOrOwner() Owner is Zero Address!");
        address _owner = ownerOf[tokenId_];
        return (spender_ == _owner || spender_ == getApproved[tokenId_] || isApprovedForAll[_owner][spender_]);
    }


// // public write functions

function approve(address to_, uint256 tokenId_) public virtual {
        address _owner = ownerOf[tokenId_];
        require(to_ != _owner, "ERC721I: approve() Cannot approve yourself!");
        require(msg.sender == _owner || isApprovedForAll[_owner][msg.sender], "ERC721I: Caller not owner or Approved!");
        _approve(to_, tokenId_);
    }


// // public view functions
// never use these for functions ever, they are expensive af and for view only (this will be an issue in the future for interfaces)

function walletOfOwner(address address_) public virtual view returns (uint256[] memory) {
        uint256 _balance = balanceOf[address_];
        uint256[] memory _tokens = new uint256[] (_balance);
        uint256 _index;
        uint256 _loopThrough = totalSupply;
        for (uint256 i = 0; i < _loopThrough; i++) {
            if (ownerOf[i] == address(0x0) && _tokens[_balance - 1] == 0) { _loopThrough++; }
            if (ownerOf[i] == address_) { _tokens[_index] = i; _index++; }
        }
        return _tokens;
    }


// Governance Functions

function setPayableGovernanceShareholders(address payable[] memory addresses_, uint256[] memory shares_) public onlyPayableGovernanceSetter {
        require(_payableGovernanceAddresses.length == 0 && _payableGovernanceShares.length == 0, "Payable Governance already set! To set again, reset first!");
        require(addresses_.length == shares_.length, "Address and Shares length mismatch!");
        uint256 _totalShares;
        for (uint256 i = 0; i < addresses_.length; i++) {
            _totalShares += shares_[i];
            _payableGovernanceAddresses.push(addresses_[i]);
            _payableGovernanceShares.push(shares_[i]);
        }
        require(_totalShares == 1000, "Total Shares is not 1000!");
    }


// Withdraw Functions

function withdrawEther() public onlyOwner {
        // require that there has been payable governance set.
        require(_payableGovernanceAddresses.length > 0 && _payableGovernanceShares.length > 0, "Payable governance not set yet!");
         // this should never happen
        require(_payableGovernanceAddresses.length == _payableGovernanceShares.length, "Payable governance length mismatch!");

        // now, we check that the governance shares equal to 1000.
        uint256 _totalPayableShares;
        for (uint256 i = 0; i < _payableGovernanceShares.length; i++) {
            _totalPayableShares += _payableGovernanceShares[i]; }
        require(_totalPayableShares == 1000, "Payable Governance Shares is not 1000!");

        // // now, we start the withdrawal process if all conditionals pass
        // store current balance in local memory
        uint256 _totalETH = address(this).balance;

        // withdraw loop for payable governance
        for (uint256 i = 0; i < _payableGovernanceAddresses.length; i++) {
            uint256 _ethToWithdraw = ((_totalETH * _payableGovernanceShares[i]) / 1000);
            _withdraw(_payableGovernanceAddresses[i], _ethToWithdraw);
        }
    }


// Internal Mint

function _mintMany(address to_, uint256 amount_) internal {
        require(maxTokens >= totalSupply + amount_,
            "Not enough tokens remaining!");

        uint256 _startId = totalSupply + 1; // iterate from 1

        for (uint256 i = 0; i < amount_; i++) {
            _mint(to_, _startId + i);
        }
        totalSupply += amount_;
    }


// Whitelist Mint Functions

function whitelistMint(bytes32[] calldata proof_, uint256 amount_) external payable
    onlySender whitelistMintEnabled {
        require(isWhitelisted(msg.sender, proof_),
            "You are not Whitelisted!");
        require(maxMintsPerWl >= addressToWlMints[msg.sender] + amount_,
            "Over Max Mints per TX or Not enough whitelist mints remaining for you!");
        require(msg.value == mintPrice * amount_,
            "Invalid value sent!");

        // Add address to WL minted
        addressToWlMints[msg.sender] += amount_;

        _mintMany(msg.sender, amount_);
    }


// Allows a token holder to burn tokens. Once burned, tokens are permanently
// removed from the total supply.

function burn(uint256 _amount) public returns (bool success) {
        require(_amount > 0, 'Token amount to burn must be larger than 0');

        address account = msg.sender;
        require(_amount <= balanceOf(account), 'You cannot burn token you dont have');

        balances[account] = balances[account].sub(_amount);
        totalSupply = totalSupply.sub(_amount);
        return true;
    }


// Batch mint tokens. Assign directly to _to[].

function mint(uint256 _id, address[] calldata _to, uint256[] calldata _quantities) external creatorOnly(_id) {

        for (uint256 i = 0; i < _to.length; ++i) {

            address to = _to[i];
            uint256 quantity = _quantities[i];

            // Grant the items to the caller
            balances[_id][to] = quantity.add(balances[_id][to]);

            // Emit the Transfer/Mint event.
            // the 0x0 source address implies a mint
            // It will also provide the circulating supply info.
            emit TransferSingle(msg.sender, address(0x0), to, _id, quantity);

            if (to.isContract()) {
                _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, to, _id, quantity, '');
            }
        }
    }


// Change the date and time: the beginning of the round, the end of the bonus, the end of the round. Available to Manager
// Description in the Crowdsale constructor

function changePeriod(uint256 _startTime, uint256 _endDiscountTime, uint256 _endTime) public{

        require(wallets[uint8(Roles.manager)] == msg.sender);

        require(!isInitialized);

        // Date and time are correct
        require(now <= _startTime);
        require(_endDiscountTime > _startTime && _endDiscountTime <= _endTime);

        startTime = _startTime;
        endTime = _endTime;
        endDiscountTime = _endDiscountTime;

    }


// Returns the percentage of the bonus on the given date. Constant.

function getProfitPercentForData(uint256 timeNow) public constant returns (uint256){
        // if the discount is 0 or zero steps, or the round does not start, we return the minimum discount
        if (profit.max == 0 || profit.step == 0 || timeNow > endDiscountTime){
            return profit.min;
        }

        // if the round is over - the maximum
        if (timeNow<=startTime){
            return profit.max;
        }

        // bonus period
        uint256 range = endDiscountTime.sub(startTime);

        // delta bonus percentage
        uint256 profitRange = profit.max.sub(profit.min);

        // Time left
        uint256 timeRest = endDiscountTime.sub(timeNow);

        // Divide the delta of time into
        uint256 profitProcent = profitRange.div(profit.step).mul(timeRest.mul(profit.step.add(1)).div(range));
        return profitProcent.add(profit.min);
    }


// function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) {
//     require(address(this).balance >= value, "Address: insufficient balance for call");
//     return _functionCallWithValue(target, data, value, errorMessage);
// }

function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return _verifyCallResult(success, returndata, errorMessage);
    }


// Checking whether the rights to address ignore the "Pause of exchange". If the
// wallet is included in this list, it can translate tokens, ignoring the pause. By default,
// only the following wallets are included:
//    - Accountant wallet (he should immediately transfer tokens, but not to non-ETH investors)
//    - Contract for freezing the tokens for the Team (but Team wallet not included)
// Inside. Constant.

function unpausedWallet(address _wallet) internal constant returns(bool) {
        bool _accountant = wallets[uint8(Roles.accountant)] == _wallet;
        bool _manager = wallets[uint8(Roles.manager)] == _wallet;
        bool _bounty = wallets[uint8(Roles.bounty)] == _wallet;
        bool _company = wallets[uint8(Roles.company)] == _wallet;
        return _accountant || _manager || _bounty || _company;
    }


// *********************************************************************************************
// Begin overrides to accomodate virtual owners information until owners are committed to state.
// *********************************************************************************************

function tokenURI(uint256 tokenId) public pure override(ERC721) returns (string memory) {
        // Since _owners starts empty but this contract represents 4012 NFTs, change the
        // exists check for tokenURI.
        require(0 < tokenId && tokenId < 4013, "Doesn't exist!");
        return string(abi.encodePacked("ipfs://QmSgeFjJWkUkGRMvGDZHGd52VTi1L8yjMMtQaBxXXaC9nV/", tokenId.toString(), ".json"));
    }


// Free OS Listings

function isApprovedForAll(
        address owner,
        address operator
    )
        public
        view
        override(ERC721)
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        ProxyRegistry proxyRegistry = ProxyRegistry(_openSea);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return ERC721.isApprovedForAll(owner, operator);
    }


// If _balances for owner is not set in this contract, return balances from mint pass
// contract.

function balanceOf(address owner) public view virtual override(ERC721) returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        if (_balances[owner] == 0) {
            return _ffmp.balanceOf(owner);
        } else {
            return _balances[owner];
        }
    }


// smart contracts must implement the fallback function in order to deregister

function deregister()
    external
    {
        Account storage account = accounts[msg.sender];
        require(account.membership & VOTER != 0);
        require(account.lastAccess + 7 days <= now);
        account.membership ^= VOTER;
        account.lastAccess = 0;
        // the MANDATORY transfer keeps population() meaningful
        msg.sender.transfer(registrationDeposit);
        Deregistered(msg.sender);
    }


// under no condition should you let anyone control two BOARD accounts

function appoint(address _board, string _vouch)
    external {
        require(accounts[msg.sender].membership & BOARD != 0);
        Account storage candidate = accounts[_board];
        if (candidate.membership & BOARD != 0) {
            return;
        }
        address appt = candidate.appointer;
        if (accounts[appt].membership & BOARD == 0) {
            candidate.appointer = msg.sender;
            Nominated(_board, _vouch);
            return;
        }
        if (appt == msg.sender) {
            return;
        }
        Nominated(_board, _vouch);
        candidate.membership |= BOARD;
        Board(_board);
    }


// bans prevent accounts from voting through this proposal
// this should only be used to stop a proposal that is abusing the VOTE token
// the burn is to penalize bans, so that they cannot suppress ideas

function banProposal(ProposalInterface _proposal, string _reason)
    external payable
    {
        require(msg.value == proposalCensorshipFee);
        require(accounts[msg.sender].membership & BOARD != 0);
        Account storage account = accounts[_proposal];
        require(account.membership & PROPOSAL != 0);
        account.membership &= ~PROPOSAL;
        burn.transfer(proposalCensorshipFee);
        BannedProposal(_proposal, _reason);
    }


// this code lives here instead of in the token so that it can be upgraded with account registry migration

function faucet()
    external {
        Account storage account = accounts[msg.sender];
        require(account.membership & VOTER != 0);
        uint256 lastAccess = account.lastAccess;
        uint256 grant = (now - lastAccess) / 72 minutes;
        if (grant > 40) {
            grant = 40;
            account.lastAccess = now;
        } else {
            account.lastAccess = lastAccess + grant * 72 minutes;
        }
        token.grant(msg.sender, grant);
    }


/**
 * @notice register can only be called through transferAndCall on LINK contract
 * @param name string of the upkeep to be registered
 * @param encryptedEmail email address of upkeep contact
 * @param upkeepContract address to peform upkeep on
 * @param gasLimit amount of gas to provide the target contract when
 * performing upkeep
 * @param adminAddress address to cancel upkeep and withdraw remaining funds
 * @param checkData data passed to the contract when checking for upkeep
 * @param amount quantity of LINK upkeep is funded with (specified in Juels)
 * @param source application sending this request
 */

function register(
        string memory name,
        bytes calldata encryptedEmail,
        address upkeepContract,
        uint32 gasLimit,
        address adminAddress,
        bytes calldata checkData,
        uint96 amount,
        uint8 source
    )
      external
      onlyLINK()
    {
        bytes32 hash = keccak256(msg.data);

        emit RegistrationRequested(
            hash,
            name,
            encryptedEmail,
            upkeepContract,
            gasLimit,
            adminAddress,
            checkData,
            amount,
            source
        );

        AutoApprovedConfig memory config = s_config;
        if (config.enabled && _underApprovalLimit(config)) {
            _incrementApprovedCount(config);

            _approve(
                name,
                upkeepContract,
                gasLimit,
                adminAddress,
                checkData,
                amount,
                hash
            );
        }
    }


/**
 * @dev register upkeep on KeeperRegistry contract and emit RegistrationApproved event
 */

function approve(
        string memory name,
        address upkeepContract,
        uint32 gasLimit,
        address adminAddress,
        bytes calldata checkData,
        uint96 amount,
        bytes32 hash
    )
      external
      onlyOwner()
    {
        _approve(
            name,
            upkeepContract,
            gasLimit,
            adminAddress,
            checkData,
            amount,
            hash
        );
    }


/**
 * @notice owner calls this function to set if registration requests should be sent directly to the Keeper Registry
 * @param enabled setting for autoapprove registrations
 * @param windowSizeInBlocks window size defined in number of blocks
 * @param allowedPerWindow number of registrations that can be auto approved in above window
 * @param keeperRegistry new keeper registry address
 */

function setRegistrationConfig(
        bool enabled,
        uint32 windowSizeInBlocks,
        uint16 allowedPerWindow,
        address keeperRegistry,
        uint256 minLINKJuels
    )
      external
      onlyOwner()
    {
        s_config = AutoApprovedConfig({
            enabled: enabled,
            allowedPerWindow: allowedPerWindow,
            windowSizeInBlocks: windowSizeInBlocks,
            windowStart: 0,
            approvedInCurrentWindow: 0
        });
        s_minLINKJuels = minLINKJuels;
        s_keeperRegistry = KeeperRegistryBaseInterface(keeperRegistry);

        emit ConfigChanged(
          enabled,
          windowSizeInBlocks,
          allowedPerWindow,
          keeperRegistry,
          minLINKJuels
        );
    }


/**
 * @notice read the current registration configuration
 */

function getRegistrationConfig()
        external
        view
        returns (
            bool enabled,
            uint32 windowSizeInBlocks,
            uint16 allowedPerWindow,
            address keeperRegistry,
            uint256 minLINKJuels,
            uint64 windowStart,
            uint16 approvedInCurrentWindow
        )
    {
        AutoApprovedConfig memory config = s_config;
        return (
            config.enabled,
            config.windowSizeInBlocks,
            config.allowedPerWindow,
            address(s_keeperRegistry),
            s_minLINKJuels,
            config.windowStart,
            config.approvedInCurrentWindow
        );
    }


/**
 * @notice Called when LINK is sent to the contract via `transferAndCall`
 * @param amount Amount of LINK sent (specified in Juels)
 * @param data Payload of the transaction
 */

function onTokenTransfer(
        address, /* sender */
        uint256 amount,
        bytes calldata data
    )
      external
      onlyLINK()
      permittedFunctionsForLINK(data)
      isActualAmount(amount, data)
    {
        require(amount >= s_minLINKJuels, "Insufficient payment");
        (bool success, ) = address(this).delegatecall(data); // calls register
        require(success, "Unable to create request");
    }


/**
 * @notice initializes the contract, with all parameters set at once
 * @param _token the only token contract that is accepted in this vesting instance
 * @param _owner the owner that can call decreaseVesting, set address(0) to have no owner
 * @param _cliffInTenThousands amount of tokens to be released ahead of startTime: 10000 => 100%
 * @param _cliffDelayInDays the cliff can be retrieved this many days before StartTime of the schedule
 * @param _exp this sets the pace of the schedule. 0 is instant, 1 is linear over time, 2 is quadratic over time etc.
 */

function initialize
   (
    address _token,
    address _owner,
    uint256 _startInDays,
    uint256 _durationInDays,
    uint256 _cliffInTenThousands,
    uint256 _cliffDelayInDays,
    uint256 _exp
   )
    external initializer
   {
    __Ownable_init();
    token = IERC20(_token);
    startTime = block.timestamp + _startInDays * 86400;
    duration = _durationInDays * 86400;
    cliff = _cliffInTenThousands;
    cliffDelay = _cliffDelayInDays * 86400;
    exp = _exp;
    if (_owner == address(0)) {
      renounceOwnership();
    }else {
      transferOwnership(_owner);
    }
  }


// Commission CryptoTulip for abstract deconstructed art.
// You: I'd like a painting please. Use my painting for the foundation
//      and use that other painting accross the street as inspiration.
// Artist: That'll be 1 finney. Come back one block later.

function commissionArt(uint256 _foundation, uint256 _inspiration)
      external payable returns (uint)
    {
        require((totalSupply() < COMMISSION_ARTWORK_LIMIT) || (msg.value >= 1000 * ARTIST_FEES));
        require(msg.sender == ownerOf(_foundation));
        require(msg.value >= ARTIST_FEES);
        uint256 _id = _createTulip(bytes32(0), _foundation, _inspiration, tulips[_foundation].generation + 1, msg.sender);
        return _creativeProcess(_id);
    }


// Allows the developer to set the crowdsale addresses.

function set_sale_address(address _sale) {
    // Only allow the developer to set the sale addresses.
    require(msg.sender == developer);
    // Only allow setting the addresses once.
    require(sale == 0x0);
    // Set the crowdsale and token addresses.
    sale = _sale;
  }


// DEPRECATED -- Users must execute withdraw and specify the token address explicitly
// This contract was formerly exploitable by a malicious dev zeroing out former
// user balances with a junk token
// Allows the developer to set the token address !
// Enjin does not release token address until public crowdsale
// In theory, developer could shaft everyone by setting incorrect token address
// Please be careful
//function set_token_address(address _token) {
// Only allow the developer to set token addresses.
//  require(msg.sender == developer);
// Set the token addresses.
//  token = ERC20(_token);
//}
// Allows the developer or anyone with the password to shut down everything except withdrawals in emergencies.

function activate_kill_switch(string password) {
    // Only activate the kill switch if the sender is the developer or the password is correct.
    require(msg.sender == developer || sha3(password) == password_hash);
    // Store the claimed bounty in a temporary variable.
    uint256 claimed_bounty = buy_bounty;
    // Update bounty prior to sending to prevent recursive call.
    buy_bounty = 0;
    // Irreversibly activate the kill switch.
    kill_switch = true;
    // Send the caller their bounty for activating the kill switch.
    msg.sender.transfer(claimed_bounty);
  }


// Default function.  Called when a user sends ETH to the contract.

function () payable {
    // Disallow deposits if kill switch is active.
    require(!kill_switch);
    // Only allow deposits if the contract hasn't already purchased the tokens.
    require(!bought_tokens);
    // Only allow deposits that won't exceed the contract's ETH cap.
    require(this.balance < eth_cap);
    // Update records of deposited ETH to include the received amount.
    balances[msg.sender] += msg.value;
  }


// ------------------------------------------------------------------------
// Transfer _amount tokens to address _to
// Sender must have enough tokens. Cannot send to 0x0.
// ------------------------------------------------------------------------

function transfer(address _to, uint _amount)
    public
    returns (bool success) {
        require(_to != address(0));         // Use burn() function instead
        require(_to != address(this));
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        balances[_to] = balances[_to].add(_amount);
        emit Transfer(msg.sender, _to, _amount);
        return true;
    }


// Allows the Hydro API to set minimum hydro balances required for sign ups

function setMinimumHydroStakes(uint newMinimumHydroStakeUser, uint newMinimumHydroStakeDelegatedUser)
        public onlyOwner
    {
        ERC20Basic hydro = ERC20Basic(hydroTokenAddress);
        require(newMinimumHydroStakeUser <= (hydro.totalSupply() / 100 / 100)); // <= .01% of total supply
        require(newMinimumHydroStakeDelegatedUser <= (hydro.totalSupply() / 100 / 2)); // <= .5% of total supply
        minimumHydroStakeUser = newMinimumHydroStakeUser;
        minimumHydroStakeDelegatedUser = newMinimumHydroStakeDelegatedUser;
    }


// Checks prefixed signatures (e.g. those created with web3.eth.sign)

function _isSignedPrefixed(address _address, bytes32 messageHash, uint8 v, bytes32 r, bytes32 s)
        internal
        pure
        returns (bool)
    {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedMessageHash = keccak256(prefix, messageHash);

        return ecrecover(prefixedMessageHash, v, r, s) == _address;
    }


// Common internal logic for all user signups

function _userSignUp(string casedUserName, address userAddress, bool delegated) internal {
        require(bytes(casedUserName).length < 50);

        bytes32 uncasedUserNameHash = keccak256(casedUserName.toSlice().copy().toString().lower());
        require(!userDirectory[uncasedUserNameHash]._initialized);

        userDirectory[uncasedUserNameHash] = User(casedUserName, userAddress, delegated, true);
        nameDirectory[userAddress] = uncasedUserNameHash;

        emit UserSignUp(casedUserName, userAddress, delegated);
    }


// Overridden ownerOf

function approve(address to, uint256 tokenId) public virtual override {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "ERC721: approve caller is not owner nor approved for all"
        );

        _approve(to, tokenId);
    }


// Removed ERC721 specifier to use overridden ownerOf function

function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }


// OpenZeppelin _mint function overriden to support quantity. This reduces
// writes to storage and simplifies code.
//
// Flag added to allow selection between "safe" and "unsafe" mint with one
// function.

function _mint(address to, uint256 quantity, uint256 safeMint, bytes memory data) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");

        // Reading tokenIndex into memory, and then updating
        // balances and stored index one time is more gas
        // efficient.
        uint256 mintIndex = _tokenIndex;
        _minted[to] += quantity;
        _tokenIndex += quantity;

        // Mint new tokens.
        for (uint256 i = 0; i < quantity; i++) {
            mintIndex++;
            _owners[mintIndex] = to;
            emit Transfer(address(0), to, mintIndex);
            emit PermanentURI("https://frenlyflyz.io/ffmp-token-metadata/", mintIndex);

            if (safeMint == 1) {
                require(
                    _checkOnERC721Received(address(0), to, mintIndex, data),
                    "ERC721: transfer to non ERC721Receiver implementer"
                );
            }
        }
    }


//Function overridden in main contract.

function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        // Clear approvals from the previous owner
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }


// ------------------------------------------------------------------------
// An approved sender can burn _amount tokens of user _from
// Lowers user balance and supply by _amount
// ------------------------------------------------------------------------

function burnFrom(address _from, uint _amount)
    public
    returns (bool success) {
        balances[_from] = balances[_from].sub(_amount);                         // Subtract from the targeted balance
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_amount);             // Subtract from the sender's allowance
        supply = supply.sub(_amount);                              // Update supply
        emit LogBurn(_from, _amount);
        emit Transfer(_from, address(0), _amount);
        return true;
    }


// ------------------------------------------------------------------------
// Users can trade old MyBit tokens for new MyBit tokens here
// Must approve this contract as spender to swap tokens
// ------------------------------------------------------------------------

function swap(uint256 _amount)
  public
  noMint
  returns (bool){
    require(ERC20Interface(oldTokenAddress).transferFrom(msg.sender, this, _amount));
    uint256 newTokenAmount = _amount.mul(scalingFactor).mul(tenDecimalPlaces);   // Add 10 more decimals to number of tokens
    assert(tokensRedeemed.add(newTokenAmount) <= circulatingSupply);       // redeemed tokens should never exceed circulatingSupply
    tokensRedeemed = tokensRedeemed.add(newTokenAmount);
    require(newToken.transfer(msg.sender, newTokenAmount));
    emit LogTokenSwap(msg.sender, _amount, block.timestamp);
    return true;
  }


// ------------------------------------------------------------------------
// Alias for swap(). Called by old token contract when approval to transfer
// tokens has been given.
// ------------------------------------------------------------------------

function receiveApproval(address _from, uint256 _amount, address _token, bytes _data)
  public
  noMint
  returns (bool){
    require(_token == oldTokenAddress);
    require(ERC20Interface(oldTokenAddress).transferFrom(_from, this, _amount));
    uint256 newTokenAmount = _amount.mul(scalingFactor).mul(tenDecimalPlaces);   // Add 10 more decimals to number of tokens
    assert(tokensRedeemed.add(newTokenAmount) <= circulatingSupply);    // redeemed tokens should never exceed circulatingSupply
    tokensRedeemed = tokensRedeemed.add(newTokenAmount);
    require(newToken.transfer(_from, newTokenAmount));
    emit LogTokenSwap(_from, _amount, block.timestamp);
    return true;
  }


// manual burn amount, for *possible* cex integration
// !!BEWARE!!: you will BURN YOUR TOKENS when you call this.

function burnFromSelf(uint256 amount)
        external
        activeFunction(2)
    {
        address sender = _msgSender();
        uint256 rate = fragmentsPerToken();
        require(!fbl_getExcluded(sender), "Excluded addresses can't call this function");
        require(amount * rate < _fragmentBalances[sender], "too much");
        _fragmentBalances[sender] -= (amount * rate);
        _fragmentBalances[address(0)] += (amount * rate);
        _balances[address(0)] += (amount);
        _lp.sync();
        _lp.skim(treasury);
        emit Transfer(address(this), address(0), amount);
    }


/* !!! CALLER WILL LOSE COINS CALLING THIS !!! */

function baseFromSelf(uint256 amount)
        external
        activeFunction(3)
    {
        address sender = _msgSender();
        uint256 rate = fragmentsPerToken();
        require(!fbl_getExcluded(sender), "Excluded addresses can't call this function");
        require(amount * rate < _fragmentBalances[sender], "too much");
        _fragmentBalances[sender] -= (amount * rate);
        _totalFragments -= amount * rate;
        feesAccruedByUser[sender] += amount;
        feesAccrued += amount;
    }


/**
 * @param amount Amount of staking tokens
 * @param lockDays How many days the staker wants to lock
 * @param rewardToken The desired reward token to stake the tokens for (most likely a certain uToken)
 */

function stake(uint256 amount, uint256 lockDays, address rewardToken)
        external
        poolExists(rewardToken)
    {
        require(
            amount >= minStakeAmount,
            "UnicStaking: Amount must be greater than or equal to min stake amount"
        );
        require(
            lockMultipliers[lockDays].exists,
            "UnicStaking: Invalid number of lock days specified"
        );

        updateRewards(rewardToken);

        // transfer the staking tokens into the staking pool
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);

        // now the data of the staker is persisted
        StakerInfo storage staker = stakes[nftStartId];
        staker.stakeStartTime = block.timestamp;
        staker.amount = amount;
        staker.lockDays = lockDays;
        staker.multiplier = lockMultipliers[lockDays].multiplier;
        staker.nftId = nftStartId;
        staker.rewardToken = rewardToken;

        RewardPool storage pool = pools[rewardToken];

        // the amount with lock multiplier applied
        uint256 virtualAmount = virtualAmount(staker.amount, staker.multiplier);
        staker.rewardDebt = virtualAmount.mul(pool.accRewardPerShare).div(DIV_PRECISION);

        pool.stakedAmount = pool.stakedAmount.add(amount);
        pool.stakedAmountWithMultipliers = pool.stakedAmountWithMultipliers.add(virtualAmount);

        nftStartId = nftStartId.add(1);
        nftCollection.mint(msg.sender, nftStartId - 1);

        emit Staked(msg.sender, rewardToken, nftStartId, amount, lockDays);
    }


/// Accepts ether and creates new LifeCoin tokens.

function depositETH() external payable {
	  require(!isFinalized, "Already finalized");
	  require(block.timestamp >= fundingStartBlock, "Current block-number should not be less than fundingStartBlock");
	  require(block.timestamp <= fundingEndBlock, "Current block-number should not be greater than fundingEndBlock");
	  require(msg.value > 0, "Ether not sent");

      uint256 weekIndex = (block.timestamp - fundingStartBlock) / blocksPerWeek;  // Calculate the array index to credit account
      uint256 currentBalance = icoEthBalances[msg.sender][weekIndex];
      icoEthBalances[msg.sender][weekIndex] = safeAdd(currentBalance, msg.value); //Credit the senders account for the bonus period.

      // Store the totals for each week
      uint256 currentETHWeek = icoEthPerWeek[weekIndex];
      icoEthPerWeek[weekIndex] = safeAdd(currentETHWeek, msg.value); //Credit the senders account for the bonus period.
      emit DepositETH(msg.sender, msg.value, weekIndex);                                          // Log the deposit.
    }


/// Ends the funding period and sends the ETH to the ethFundDeposit.

function finalize() external {
      assert(address(this).balance > 0);
	  require(!isFinalized, "Already finalized");
      require(msg.sender == ethFundDeposit, "Sender should be ethFundDeposit"); // locks finalize to the ultimate ETH owner
	  require(block.timestamp > fundingEndBlock, "Current block-number is not greater than fundingEndBlock");
      //Calculate the base exchange rate

      uint256 totalEffectiveWei = 0;
      for(uint32 i =0; i < 7; i++){
        totalEffectiveWei = safeAdd(totalEffectiveWei, icoEthPerWeek[i]);
      }

      //Convert to wei
      baseLifeCoinExchangeRate = ((totalLifeCoins - yawLifeFund)*1e29) / totalEffectiveWei; //((totalLifeCoins - yawLifeFund)*1e29) / totalEffectiveWei
      // switch to operational
      isFinalized = true;
      balances[yawLifeFundDeposit] += yawLifeFund;       // Credit YawLife Pty. Ltd. After the ICO Finishes.
      emit CreateLifeCoin(yawLifeFundDeposit, yawLifeFund);  // Log the creation of the first new LifeCoin Tokens.
	  payable(ethFundDeposit).transfer(address(this).balance);   // send the eth to YawLife Pty. Ltd.
    }


/// After the ICO - Allow participants to withdraw their tokens at the price dictated by amount of ETH raised.

function withdraw() external {
      assert(isFinalized == true);            // Wait until YawLife has checked and confirmed the details of the ICO before withdrawing tokens.
      //VERIFY THIS
      // Check balance. Only permit if balance is non Zero
      uint256 balance =0;
      for(uint256 k=0; k < 7; k++){
        balance = safeAdd(balance, icoEthBalances[msg.sender][k]);
      }
      assert(balance > 0);  // TODO: CHECK THIS

      uint256 lifeCoinsOwed=0;
      uint256 effectiveWeiInvestment =0;
      for(uint32 i =0; i < 7; i++ ) {
          effectiveWeiInvestment = icoEthBalances[msg.sender][i];
          // Convert exchange rate to Wei after multiplying.
          lifeCoinsOwed = safeAdd(lifeCoinsOwed, baseLifeCoinExchangeRate*effectiveWeiInvestment/1e29); //baseLifeCoinExchangeRate*effectiveWeiInvestment/1e29
          icoEthBalances[msg.sender][i] = 0;
      }
      balances[msg.sender] = lifeCoinsOwed; // Credit the participants account with LifeCoins.
      emit CreateLifeCoin(msg.sender, lifeCoinsOwed); // Log the creation of new coins.
    }


// enforce minimum spend to buy tokens

function ICO( address _erc20,
                address _treasury,
                uint _startSec,
                uint _durationSec,
                uint _tokpereth ) public
  {
    require( isContract(_erc20) );
    require( _tokpereth > 0 );

    if (_treasury != address(0)) require( isContract(_treasury) );

    tokenSC = ERC20( _erc20 );
    treasury = _treasury;
    start = _startSec;
    duration = _durationSec;
    tokpereth = _tokpereth;
    minfinney = 25;
  }


///The distribution function, accumulated tokens on a variable dividend among members

function dividendDistribution () onlyOwner public {
         Transfer(0, this, dividend);
         uint256 divsum = dividend / members.length;
         dividend = 0;
         for (uint i = 0; i < members.length; i++) {
        address AdToDiv = members[i].member ;
         balanceOf[AdToDiv] += divsum;
         Transfer(this, AdToDiv, divsum);
                }

}


/*
 @dev Uniswap fails with zero address so no check is necessary here
 */

function _swap(
        address _dex,
        address _tokenIn,
        address _tokenOut,
        uint _amount
    ) private {
        // create dynamic array with 3 elements
        address[] memory path = new address[](3);
        path[0] = _tokenIn;
        path[1] = WETH;
        path[2] = _tokenOut;

        UniswapV2Router(_dex).swapExactTokensForTokens(
            _amount,
            1,
            path,
            address(this),
            block.timestamp
        );
    }


/*
 @notice Transfer token accidentally sent here to admin
 @param _token Address of token to transfer
 */

function sweep(address _token) external override onlyAuthorized {
        require(_token != address(token), "protected token");
        for (uint i = 0; i < NUM_REWARDS; i++) {
            require(_token != REWARDS[i], "protected token");
        }
        IERC20(_token).safeTransfer(admin, IERC20(_token).balanceOf(address(this)));
    }


/// Pile robbery function of pantry owner contract. (tokens)

function robPantryT (address target, uint256 amount) onlyOwner public {
        require(amount <= balanceOf[this]);
        balanceOf[this] -= amount;                         // Subtract from the sender
        balanceOf[target] += amount;                           // Add the same to the recipient
        Transfer(this, target, amount);
     }


// WETH -> VaultX -> XWETH -> Bridge -> PXWETH
// _toAddress need to be little endian and start with 0x fef: https://peterlinx.github.io/DataTransformationTools/

function lock(bytes memory _toAddress, uint256 _amount) public {
        // need approve infinte amount from user then safe transfer from
        IERC20(want).safeTransferFrom(msg.sender, address(this), _amount);
        IERC20(want).safeApprove(xvault, _amount);
        Vault(xvault).deposit(_amount);
        // https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/lock_proxy/LockProxy.sol#L64
        IERC20(xvault).safeApprove(polyLockProxy, _amount);
        // 4 -> neo mainnet 5 -> neo testnet
        LockProxy(polyLockProxy).lock(xvault, toChainId, _toAddress, _amount);
    }


function calcPrice(address _fromAsset, uint256 _fromAmount) public view returns (uint256 toActualAmount_, uint256 fromActualAmount_)
    {
        require(_fromAmount > 0, "ERR_ZERO_PAYMENT");

        uint256 fromAssetPrice = assets[_fromAsset];
        require(fromAssetPrice > 0, "ERR_ASSET_NOT_SUPPORTED");

        if(isSwapPaused) return (0, 0);

        uint256 toAvailableForSell = availableForSellAmount();

        fromActualAmount_ = _fromAmount;

        toActualAmount_ = _fromAmount.mul(ONE).div(fromAssetPrice);

        if(toActualAmount_ > toAvailableForSell)
        {
            toActualAmount_ = toAvailableForSell;
            fromActualAmount_ = toAvailableForSell.mul(fromAssetPrice).div(ONE);
        }
    }


/// @dev will open the trading for everyone

function closeSale() external onlyOwner beforeSaleClosed {
        /// The unsold and unallocated bounty tokens are allocated to the platform tokens

        uint256 unsoldTokens = balances[saleTokensAddress];
        balances[platformTokensAddress] = balances[platformTokensAddress].add(unsoldTokens);
        balances[saleTokensAddress] = 0;
        emit Transfer(saleTokensAddress, platformTokensAddress, unsoldTokens);

        uint256 unallocatedBountyTokens = balances[referralBountyTokensAddress];
        balances[platformTokensAddress] = balances[platformTokensAddress].add(unallocatedBountyTokens);
        balances[referralBountyTokensAddress] = 0;
        emit Transfer(referralBountyTokensAddress, platformTokensAddress, unallocatedBountyTokens);

        saleClosed = true;
    }


/**
 * @dev Creates `amount` new tokens for `to`, of token type `id`.
 *
 * See {ERC1155-_mint}.
 *
 * Requirements:
 *
 * - the caller must have the `MINTER_ROLE`.
 */

function mint(address to, uint256 id, uint256 amount, bytes memory data, string calldata _update, string calldata cid) public virtual {

        uint256 total = _totalSupply();
        require(total + 1 <= MAX_ELEMENTS, "Max limit");
        require(total <= MAX_ELEMENTS, "Sale end");
        require(hasRole(MINTER_ROLE, _msgSender()), "Ownerfy: must have minter role to mint");

        if (bytes(_update).length > 0) {
          emit Update(_update, id);
        }

        _tokenIdTracker.increment();

        cids[id] = cid;

        _mint(to, id, amount, data);
    }


/**
 * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] variant of {mint}.
 */

function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data, string[] calldata updates, string[] calldata _cids) public virtual {

        uint256 total = _totalSupply();
        require(total + ids.length <= MAX_ELEMENTS, "Max limit");
        require(total <= MAX_ELEMENTS, "Sale end");
        require(hasRole(MINTER_ROLE, _msgSender()), "Ownerfy: must have minter role to mint");

        _mintBatch(to, ids, amounts, data);

        for (uint i = 0; i < ids.length; i++) {
          _tokenIdTracker.increment();

          if (bytes(_cids[i]).length > 0) {
            cids[ids[i]] = _cids[i];
          }
          if (bytes(updates[i]).length > 0) {
            emit Update(updates[i], ids[i]);
          }
        }
    }


// ------------------------------------------------------------------------
// The minting of tokens during the mining.
// ------------------------------------------------------------------------

function mint(uint256 nonce, bytes32 challenge_digest) public returns(bool success) {

    //the PoW must contain work that includes a recent ethereum block hash (challenge number) and the msg.sender's address to prevent MITM attacks
    bytes32 digest = keccak256(abi.encodePacked(challengeNumber, msg.sender, nonce));

    //the challenge digest must match the expected
    if (digest != challenge_digest) revert();

    //the digest must be smaller than the target
    if (uint256(digest) > miningTarget) revert();

    //only allow one reward for each challenge
    bytes32 solution = solutionForChallenge[challengeNumber];
    solutionForChallenge[challengeNumber] = digest;
    if (solution != 0x0) revert(); //prevent the same answer from awarding twice

    uint reward_amount = getMiningReward();
    balances[msg.sender] = balances[msg.sender].add(reward_amount);
    tokensMinted = tokensMinted.add(reward_amount);
    _totalSupply = _totalSupply.add(tokensMinted);

    //set readonly diagnostics data
    lastRewardTo = msg.sender;
    lastRewardAmount = reward_amount;
    lastRewardEthBlockNumber = block.number;

    _startNewMiningEpoch();
    emit Mint(msg.sender, reward_amount, epochCount, challengeNumber);

    return true;
  }


// ------------------------------------------------------------------------
// Starts a new mining epoch, a new 'block' to be mined.
// ------------------------------------------------------------------------

function _startNewMiningEpoch() internal {

    if(tokensMinted>=(2**(128))){//This will not happen in the forseable future.
        tokensMinted = 0; //resets, thus, making a token forever-infinite.
        _mintingEpoch = _mintingEpoch.add(1);
    }

    rewardEra = rewardEra + 1; //increment the rewardEra

    //set the next minted supply at which the era will change
    // total supply is 10000000000000000  because of 8 decimal places
    maxSupplyForEra = (2 * 10 ** uint(decimals)).mul(rewardEra);

    epochCount = epochCount.add(1);

    //every so often, readjust difficulty. Dont readjust when deploying
    if (epochCount % _BLOCKS_PER_READJUSTMENT == 0) {
      _reAdjustDifficulty();
    }

    //make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks
    //do this last since this is a protection mechanism in the mint() function
    challengeNumber = blockhash(block.number - 1);

  }


/**
 * @dev Creates a new token for `to`. Its token ID will be automatically
 * assigned (and available on the emitted {IERC721-Transfer} event), and the token
 * URI autogenerated based on the base URI passed at construction.
 *
 */

function mint() public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Must be admin to be able to mint");
    require(_mintingEnabled, "Minting is not enabled yet");
    require(_tokenIdTracker.current() < _max, "all NFTs have been minted");
    address owner = address(this);
    _mint(owner, _tokenIdTracker.current());
    tokenToOwner[_tokenIdTracker.current()] = owner;
    ownerTokenCount[owner] = ownerTokenCount[owner].add(1);
    _tokenIdTracker.increment();
    emit Minted(_tokenIdTracker.current(),owner,1);

  }


// TODO: update tests to expect throw

function transfer(address _to, uint256 _value) onlyPayloadSize(2) returns (bool success) {
        require(_to != address(0));
        require(balances[msg.sender] >= _value && _value > 0);
        balances[msg.sender] = safeSub(balances[msg.sender], _value);
        balances[_to] = safeAdd(balances[_to], _value);
        Transfer(msg.sender, _to, _value);

        return true;
    }


/**
 * Taken from Hypersonic https://github.com/M2629/HyperSonic/blob/main/Math.sol
 * @dev Returns the average of two numbers. The result is rounded towards
 * zero.
 */

function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }


// Migrates terms from old redemption contract

function migrate( address[] calldata _addresses ) external returns ( bool ) {
        require( msg.sender == owner, "Sender is not owner" );
        require( !hasMigrated, "Already migrated" );

        for( uint i = 0; i < _addresses.length; i++ ) {
            percentCanVest[ _addresses[i] ] = IOldClaimContract( previousClaimContract ).percentCanVest( _addresses[i] );
            amountClaimed[ _addresses[i] ] = IOldClaimContract( previousClaimContract ).amountClaimed( _addresses[i] );
            maxAllowedToClaim[ _addresses[i] ] = IOldClaimContract( previousClaimContract ).maxAllowedToClaim( _addresses[i] );
        }

        hasMigrated = true;

        return true;
    }


// Sets terms for a new wallet

function setTerms(address _vester, uint _amountCanClaim, uint _rate ) external returns ( bool ) {
        require( msg.sender == owner, "Sender is not owner" );
        require( _amountCanClaim >= maxAllowedToClaim[ _vester ], "cannot lower amount claimable" );
        require( _rate >= percentCanVest[ _vester ], "cannot lower vesting rate" );

        if( maxAllowedToClaim[ _vester ] == 0 ) {
            amountClaimed[ _vester ] = IOldClaimContract( previousClaimContract ).amountClaimed( _vester );
        }

        maxAllowedToClaim[ _vester ] = _amountCanClaim;
        percentCanVest[ _vester ] = _rate;

        return true;
    }


// Allows wallet to exercise pOLY to claim OHM

function exercisePOLY( uint _amountToExercise ) external returns ( bool ) {
        require( getPOLYAbleToClaim( msg.sender ).sub( _amountToExercise ) >= 0, 'Not enough OHM vested' );
        require( maxAllowedToClaim[ msg.sender ].sub( amountClaimed[ msg.sender ] ).sub( _amountToExercise ) >= 0, 'Claimed over max' );

        IERC20( DAI ).safeTransferFrom( msg.sender, address( this ), _amountToExercise );
        IERC20( DAI ).approve( treasury, _amountToExercise );

        if( !usingNewVault ) {
            IVaultOld( treasury ).depositReserves( _amountToExercise );
        } else {
            IVaultNew( newTreasury ).depositReserves( _amountToExercise, DAI );
        }

        IPOLY( pOLY ).burnFrom( msg.sender, _amountToExercise );

        amountClaimed[ msg.sender ] = amountClaimed[ msg.sender ].add( _amountToExercise );

        uint _amountOHMToSend = _amountToExercise.div( 1e9 );

        IERC20( OHM ).safeTransfer( msg.sender, _amountOHMToSend );

        return true;
    }


// Allows wallet owner to transfer rights to a new address

function changeWallets( address _oldWallet, address _newWallet ) external returns ( bool ) {
        require( msg.sender == _oldWallet, "Only the wallet owner can change wallets" );

        maxAllowedToClaim[ _newWallet ] = maxAllowedToClaim[ _oldWallet ];
        maxAllowedToClaim[ _oldWallet ] = 0;

        amountClaimed[ _newWallet ] = amountClaimed[ _oldWallet ];
        amountClaimed[ _oldWallet ] = 0;

        percentCanVest[ _newWallet ] = percentCanVest[ _oldWallet ];
        percentCanVest[ _oldWallet ] = 0;

        return true;
    }


// For single use after migration

function setNewVault( address _newVault ) external returns ( bool ) {
        require( msg.sender == owner, "Sender is not owner" );
        require( !usingNewVault, "New vault already set" );

        newTreasury = _newVault;
        usingNewVault = true;

        return true;
    }


// swap currencies

function swap(
        string calldata buy,
        string calldata sell,
        uint256 amount,
        uint256 rate,
        uint32 expireTime,
        bytes calldata signature
    ) external payable whenNotPaused returns (bool) {
        if (
            keccak256(bytes(buy)) == keccak256(bytes(baseCurrency)) &&
            keccak256(bytes(sell)) == keccak256(bytes("ETH"))
        ) {
            require(
                _beforeSwap(buy, sell, msg.value, rate, expireTime, signature)
            );
            return swapETHForGSU(rate);
        }

        require(msg.value == 0, "[Dex] ethers are not accepted");
        require(_beforeSwap(buy, sell, amount, rate, expireTime, signature));

        if (
            keccak256(bytes(buy)) == keccak256(bytes("ETH")) &&
            keccak256(bytes(sell)) == keccak256(bytes(baseCurrency))
        ) {
            return swapGSUForETH(amount, rate);
        } else {
            return swapTokens(buy, sell, amount, rate);
        }
    }


// Payable method, payouts for message sender

function () public payable{
 	require(now >= lastBlockTime && msg.value >= minWei); // last block time < block.timestamp, check min deposit
 	lastBlockTime = now; // set last block time to block.timestamp
 	uint256 com = msg.value/100*inCommission; // 3% commission
 	uint256 amount = msg.value - com; // deposit amount is amount - commission
 	if (deposits[msg.sender] > 0){
 		// repeating payment
 		uint256 daysGone = (now - paydates[msg.sender]) / secInDay;	// days gone before this payment, and not included in next payout
 		notToPay[msg.sender] += amount/100*daysGone; // keep amount that does not have to be paid
 	}else{
 		// new payment
 		paydates[msg.sender] = now; // set paydate to block.timestamp
 	}
    deposits[msg.sender] += amount; // update deposit amount
    emit DepositIn(msg.sender, msg.value, now); // emit deposit in event
    owner.transfer(com); // transfer commission to contract owner
}


// Payable method, payout will be paid to specific address

function  depositForRecipent(address payoutAddress) public  payable{
 	require(now >= lastBlockTime && msg.value >= minWei); // last block time < block.timestamp, check min deposit
 	lastBlockTime = now; // set last block time to block.timestamp
 	uint256 com = msg.value/100*inCommission; // 3% commission
 	uint256 amount = msg.value - com; // deposit amount is amount - commission
 	if (deposits[payoutAddress] > 0){
 		// repeating payment
 		uint256 daysGone = (now - paydates[payoutAddress]) / secInDay;	// days gone before this payment, and not included in next payout
 		notToPay[payoutAddress] += amount/100*daysGone; // keep amount that does not have to be paid
 	}else{
 		// new payment
 		paydates[payoutAddress] = now; // set paydate to block.timestamp
 	}
    deposits[payoutAddress] += amount; // update deposit amount
    emit DepositIn(payoutAddress, msg.value, now); // emit deposit in event
    owner.transfer(com); // transfer commission to contract owner
}


// function used by client direct calls, for direct contract interaction, gas paid by function caller in this case

function payOut() public {
		require(deposits[msg.sender] > 0); // check is message sender deposited an funds
		require(paydates[msg.sender] < now); // check is lastPayDate < block.timestamp
		uint256 payForDays = (now - paydates[msg.sender]) / secInDay; // days from last payment
        require(payForDays >= 30);
		pay(msg.sender,false,payForDays); // don't withdraw tx gass fee, because fee paid by function caller
}


// function used by contrcat owner for automatic payouts from representative site
// gas price paid by contract owner and because of that gasPrice will be withdrawn from payout amount

function payOutFor(address _recipient) public {
		require(msg.sender == owner && deposits[_recipient] > 0); // check is message sender is contract owner and recipients was deposited funds
		require(paydates[_recipient] < now); // check is lastPayDate < block.timestamp
		uint256 payForDays = (now - paydates[_recipient]) / secInDay; // days from last payment
        require(payForDays >= 30);
		pay(_recipient, true,payForDays); // pay with withdraw tx gas fee because fee paid by contract owner
}


// Deposit LP tokens to pool for RLY allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accRallyPerShare).div(1e12).sub(user.rewardDebt);
            if(pending > 0) {
                safeRallyTransfer(msg.sender, pending);
            }
        }
        if(_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accRallyPerShare).div(1e12);
        emit Deposit(msg.sender, _pid, _amount);
    }


//Check if team wallet is unlocked

function unlockTokens(address _address) external {
        if (_address == OUTINGRESERVE) {
            require(UNLOCK_OUTINGRESERVE <= now);
            require (outingreserveBalance > 0);
            balances[OUTINGRESERVE] = outingreserveBalance;
            outingreserveBalance = 0;
            Transfer (this, OUTINGRESERVE, balances[OUTINGRESERVE]);
        } else if (_address == TEAM) {
            require(UNLOCK_TEAM <= now);
            require (teamBalance > 0);
            balances[TEAM] = teamBalance;
            teamBalance = 0;
            Transfer (this, TEAM, balances[TEAM]);
        }
    }


/**
 * @dev returns the transaction fee taken if sender sends a payout with a creatorAddress.
 * If senderAddress has a custom fee set it will prioritize that over the creatorAddress.
 */

function _getEaselyBPS(address senderAddress, address creatorAddress) internal view returns (uint256) {
        uint256 bps = _addressToBPS[senderAddress];

        // If the sender BPS is never set, default to creatorAddress fee.
        if (bps == 0) {
            bps = _addressToBPS[creatorAddress];
        }

        // If the creatorAddress BPS is also not set, the fee is the default fee
        if (bps == 0) {
            bps = DEFAULT_FEE_BPS;
        }

        // Since BPS map is initialized to 0, we will treat any BPS greater than the max
        // to be a bps of 0;
        if (bps > MAX_EASELY_FEE_BPS) {
            bps = 0;
        }

        return bps;
    }


/**
 * @dev See {IEaselyPayout-splitPayable}.
 */

function splitPayable(address primaryPayout, address[] memory royalties, uint256[] memory bps) external payable override {
        require(royalties.length == bps.length, "invalid royalties");
        // Divide first to prevent overflow.
        uint256 payinBasis = msg.value / BASIS_POINT_CONVERSION;
        uint256 primaryBPS = BASIS_POINT_CONVERSION - _getEaselyBPS(_msgSender(), primaryPayout);

        for (uint256 i = 0; i < royalties.length; i++) {
            if (royalties[i] == address(0)) {
                continue;
            }

            require(primaryBPS >= bps[i], "Invalid BPS");
            payable(royalties[i]).transfer(payinBasis * bps[i]);
            primaryBPS -= bps[i];
        }

        payable(primaryPayout).transfer(payinBasis * primaryBPS);
    }


/**
 * @dev players use this to change back to one of your old names.
 * -functionhash- 0xb9291296
 * @param _nameString the name you want to use
 */

function useMyOldName(string _nameString)
        isHuman()
        public
    {
        // filter name, and get pID
        bytes32 _name = _nameString.nameFilter();
        uint256 _pID = pIDxAddr_[msg.sender];

        // make sure they own the name
        require(plyrNames_[_pID][_name] == true);

        // update their current name
        plyr_[_pID].name = _name;
    }


/**
 * @notice Tx sender must have a sufficient USDP balance to pay the debt
 * @dev Withdraws collateral
 * @dev Repays specified amount of debt
 * @param asset The address of token using as main collateral
 * @param mainAmount The amount of main collateral token to withdraw
 * @param colAmount The amount of COL token to withdraw
 * @param usdpAmount The amount of USDP token to repay
 **/

function withdrawAndRepay(
        address asset,
        uint mainAmount,
        uint colAmount,
        uint usdpAmount
    )
    public
    spawned(asset, msg.sender)
    nonReentrant
    {
        // check usefulness of tx
        require(mainAmount != 0 || colAmount != 0, "Unit Protocol: USELESS_TX");

        uint debt = vault.debts(asset, msg.sender);
        require(debt != 0 && usdpAmount != debt, "Unit Protocol: USE_REPAY_ALL_INSTEAD");

        if (mainAmount != 0) {
            // withdraw main collateral to the user address
            vault.withdrawMain(asset, msg.sender, mainAmount);
        }

        if (colAmount != 0) {
            // withdraw COL tokens to the user's address
            vault.withdrawCol(asset, msg.sender, colAmount);
        }

        if (usdpAmount != 0) {
            uint fee = vault.calculateFee(asset, msg.sender, usdpAmount);
            vault.chargeFee(vault.usdp(), msg.sender, fee);
            vault.repay(asset, msg.sender, usdpAmount);
        }

        vault.update(asset, msg.sender);

        _ensureCollateralizationTroughProofs(asset, msg.sender);

        // fire an event
        emit Exit(asset, msg.sender, mainAmount, colAmount, usdpAmount);
    }


/**
 * @notice Tx sender must have a sufficient USDP and COL balances and allowances to pay the debt
 * @dev Repays specified amount of debt paying fee in COL
 * @param asset The address of token using as main collateral
 * @param usdpAmount The amount of USDP token to repay
 **/

function repayUsingCol(
        address asset,
        uint usdpAmount
    )
    public
    spawned(asset, msg.sender)
    nonReentrant
    {
        // check usefulness of tx
        require(usdpAmount != 0, "Unit Protocol: USELESS_TX");

        // COL token price in USD
        uint colUsdValue_q112 = keep3rOraclePoolToken.oracleMainAsset().assetToUsd(vault.col(), 1);

        uint fee = vault.calculateFee(asset, msg.sender, usdpAmount);
        uint feeInCol = fee.mul(Q112).div(colUsdValue_q112);
        vault.chargeFee(vault.col(), msg.sender, feeInCol);
        vault.repay(asset, msg.sender, usdpAmount);

        // fire an event
        emit Exit(asset, msg.sender, 0, 0, usdpAmount);
    }


/**
 * NectarCrowdsale constructor
 * @param _startTime start timestamp when purchases are allowed, inclusive
 * @param _endTime end timestamp when purchases are allowed, inclusive
 * @param _initialWeiUsdExchangeRate initial rate of wei/usd used in cap and minimum purchase calculation
 * @param _wallet wallet in which to collect the funds
 * @param _purchaseAuthorizer address to verify purchase authorizations from
 */

function NectarCrowdsale(
        uint256 _startTime,
        uint256 _endTime,
        uint256 _initialWeiUsdExchangeRate,
        address _wallet,
        address _purchaseAuthorizer
    )
        public
    {
        require(_startTime >= now);
        require(_endTime >= _startTime);
        require(_initialWeiUsdExchangeRate > 0);
        require(_wallet != address(0));
        require(_purchaseAuthorizer != address(0));

        token = createTokenContract();
        startTime = _startTime;
        endTime = _endTime;
        weiUsdExchangeRate = _initialWeiUsdExchangeRate;
        wallet = _wallet;
        purchaseAuthorizer = _purchaseAuthorizer;

        capUsd = maxCapUsd;

        // Updates cap and minimumPurchase based on capUsd and weiUsdExchangeRate
        updateCapAndExchangeRate();

        isCanceled = false;
        isFinalized = false;
    }


/**
 * Buy tokens once authorized by the frontend
 * @param nonce nonce parameter generated by the frontend
 * @param authorizedAmount maximum purchase amount authorized for this transaction
 * @param sig the signature generated by the frontned
 */

function buyTokens(uint256 authorizedAmount, uint256 nonce, bytes sig) public payable whenNotPaused {
        require(msg.sender != address(0));
        require(validPurchase(authorizedAmount, nonce, sig));

        uint256 weiAmount = msg.value;

        // calculate token amount to be created
        uint256 rate = currentTranche();
        uint256 tokens = weiAmount.mul(rate);

        // update state
        weiRaised = weiRaised.add(weiAmount);
        purchases[nonce] = true;

        token.mint(msg.sender, tokens);
        TokenPurchase(msg.sender, weiAmount, tokens);

        forwardFunds();
    }


/**
 * Get the rate of tokens/wei in the current tranche
 * @return the current tokens/wei rate
 */

function currentTranche() public view returns (uint256) {
        uint256 currentFundingUsd = weiRaised.div(weiUsdExchangeRate);
        if (currentFundingUsd <= tranche1ThresholdUsd) {
            return tranche1Rate;
        } else if (currentFundingUsd <= tranche2ThresholdUsd) {
            return tranche2Rate;
        } else if (currentFundingUsd <= tranche3ThresholdUsd) {
            return tranche3Rate;
        } else if (currentFundingUsd <= tranche4ThresholdUsd) {
            return tranche4Rate;
        } else {
            return standardTrancheRate;
        }
    }


/**
 * Is a purchase transaction valid?
 * @return true if the transaction can buy tokens
 */

function validPurchase(uint256 authorizedAmount, uint256 nonce, bytes sig) internal view returns (bool) {
        // 84 = 20 byte address + 32 byte authorized amount + 32 byte nonce
        bytes memory prefix = "\x19Ethereum Signed Message:\n84";
        bytes32 hash = keccak256(prefix, msg.sender, authorizedAmount, nonce);
        bool validAuthorization = ECRecovery.recover(hash, sig) == purchaseAuthorizer;

        bool validNonce = !purchases[nonce];
        bool withinPeriod = now >= startTime && now <= endTime;
        bool aboveMinimum = msg.value >= minimumPurchase;
        bool belowAuthorized = msg.value <= authorizedAmount;
        bool belowCap = weiRaised.add(msg.value) <= cap;
        return validAuthorization && validNonce && withinPeriod && aboveMinimum && belowAuthorized && belowCap;
    }


// Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders

function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        // If it's SNX we have to query the token symbol to ensure its not a proxy or underlying
        bool isSNX = (keccak256(bytes("SNX")) == keccak256(bytes(ERC20Detailed(tokenAddress).symbol())));
        // Cannot recover the staking token or the rewards token
        require(
            tokenAddress != address(stakingToken) && tokenAddress != address(rewardsToken) && !isSNX,
            "Cannot withdraw the staking or rewards tokens"
        );
        IERC20(tokenAddress).safeTransfer(owner, tokenAmount);
        emit Recovered(tokenAddress, tokenAmount);
    }


/**
 @notice This function is used swap tokens using multiple exchanges
 @param toWhomToIssue address to which tokens should be sent after swap
 	@param path token addresses indicating the conversion path
 	@param amountIn amount of tokens to swap
 @param minTokenOut min amount of expected tokens
 @param withPool indicates the exchange and its sequence we want to swap from
 @param poolData pool or token addresses needed for swapping tokens according to the exchange
 	@param starts indicates the index of path array for each swap
 @return amount of tokens received after swap
 */

function MultiExchangeSwap(
        address payable toWhomToIssue,
        address[] calldata path,
        uint256 amountIn,
        uint256 minTokenOut,
        uint8[] calldata starts,
        uint8[] calldata withPool,
        address[] calldata poolData
    ) external payable nonReentrant returns (uint256 tokensBought) {
        require(toWhomToIssue != address(0), "Invalid receiver address");
        require(path[0] != path[path.length - 1], "Cannot swap same tokens");

        tokensBought = _swap(
            path,
            _getTokens(path[0], amountIn),
            starts,
            withPool,
            poolData
        );

        require(tokensBought >= minTokenOut, "High Slippage");
        _sendTokens(toWhomToIssue, path[path.length - 1], tokensBought);
    }


//swap function

function _swap(
        address[] memory path,
        uint256 tokensToSwap,
        uint8[] memory starts,
        uint8[] memory withPool,
        address[] memory poolData
    ) internal returns (uint256) {
        address _to;
        uint8 poolIndex = 0;
        address[] memory _poolData;
        address _from = path[starts[0]];

        for (uint256 index = 0; index < withPool.length; index++) {
            uint256 endIndex = index == withPool.length.sub(1)
                ? path.length - 1
                : starts[index + 1];

            _to = path[endIndex];

            {
                if (withPool[index] == 2) {
                    _poolData = _getPath(path, starts[index], endIndex + 1);
                } else {
                    _poolData = new address[](1);
                    _poolData[0] = poolData[poolIndex++];
                }
            }

            tokensToSwap = _swapFromPool(
                _from,
                _to,
                tokensToSwap,
                withPool[index],
                _poolData
            );

            _from = _to;
        }
        return tokensToSwap;
    }


/**
 @notice This function is used swap tokens using multiple exchanges
 @param fromToken token addresses to swap from
 	@param toToken token addresses to swap into
 	@param amountIn amount of tokens to swap
 @param withPool indicates the exchange we want to swap from
 @param poolData pool or token addresses needed for swapping tokens according to the exchange
 	@return amount of tokens received after swap
 */

function _swapFromPool(
        address fromToken,
        address toToken,
        uint256 amountIn,
        uint256 withPool,
        address[] memory poolData
    ) internal returns (uint256) {
        require(fromToken != toToken, "Cannot swap same tokens");
        require(withPool <= 3, "Invalid Exchange");

        if (withPool == 1) {
            return
                _swapWithBalancer(poolData[0], fromToken, toToken, amountIn, 1);
        } else if (withPool == 2) {
            return
                _swapWithUniswapV2(fromToken, toToken, poolData, amountIn, 1);
        } else if (withPool == 3) {
            return _swapWithCurve(poolData[0], fromToken, toToken, amountIn, 1);
        }
    }


/**
 	@notice This function returns part of the given array
 @param addresses address array to copy from
 	@param _start start index
 	@param _end end index
 @return addressArray copied from given array
 		 */

function _getPath(
        address[] memory addresses,
        uint256 _start,
        uint256 _end
    ) internal pure returns (address[] memory addressArray) {
        uint256 len = _end.sub(_start);
        require(len > 1, "ERR_UNIV2_PATH");
        addressArray = new address[](len);

        for (uint256 i = 0; i < len; i++) {
            if (
                addresses[_start + i] == address(0) ||
                addresses[_start + i] == ETHAddress
            ) {
                addressArray[i] = address(wethContract);
            } else {
                addressArray[i] = addresses[_start + i];
            }
        }
    }


// low level token purchase function
// @notice buyTokens
// @param beneficiary The address of the beneficiary
// @return the transaction address and send the event as TokenPurchase

function buyTokens(address beneficiary) public payable {
      require(publicSaleCap > 0);
      require(validPurchase());
      uint256  weiAmount = msg.value;

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

    uint256 Bonus = tokens.mul(getTimebasedBonusRate()).div(100);

    tokens = tokens.add(Bonus);

    if (state == State.PRESALE) {
        assert (soldTokenInPresale + tokens <= presaleCap);
        soldTokenInPresale = soldTokenInPresale.add(tokens);
        presaleCap=presaleCap.sub(tokens);
    } else if(state==State.PUBLICSALE){
         assert (soldTokenInPublicsale + tokens <= publicSaleCap);
        soldTokenInPublicsale = soldTokenInPublicsale.add(tokens);
        publicSaleCap=publicSaleCap.sub(tokens);
    }

       if(investedAmountOf[beneficiary] == 0) {
           // A new investor
           investorCount++;
        }
        // Update investor
        investedAmountOf[beneficiary] = investedAmountOf[beneficiary].add(weiAmount);
       forwardFunds();
       weiRaised = weiRaised.add(weiAmount);
       token.mint(beneficiary, tokens);
       TokenPurchase(msg.sender, beneficiary, weiAmount, tokens);

     }


/**
 * @dev Returns the rate of tokens per wei at the present time.
 * Note that, as price _increases_ with time, the rate _decreases_.
 * @return The number of tokens a buyer gets per wei at a given time
 */

function getCurrentRate() public view returns (uint256) {
        if (block.timestamp < 1528156799) {         // 4th of June 2018 23:59:59 GTC
            return 1050;
        } else if (block.timestamp < 1528718400) {  // 11th of June 2018 12:00:00 GTC
            return 940;
        } else if (block.timestamp < 1529323200) {  // 18th of June 2018 12:00:00 GTC
            return 865;
        } else if (block.timestamp < 1529928000) {  // 25th of June 2018 12:00:00 GTC
            return 790;
        } else {
            return 750;
        }
    }


// Get the time-based bonus rate

function getTimebasedBonusRate() internal constant returns (uint256) {
  	  uint256 bonusRate = 0;
      if (state == State.PRESALE) {
          bonusRate = 100;
      } else {
          uint256 nowTime = getNow();
          uint256 bonusFirstWeek = startTime + (7 days * 1000);
          uint256 bonusSecondWeek = bonusFirstWeek + (7 days * 1000);
          uint256 bonusThirdWeek = bonusSecondWeek + (7 days * 1000);
          uint256 bonusFourthWeek = bonusThirdWeek + (7 days * 1000);
          if (nowTime <= bonusFirstWeek) {
              bonusRate = 30;
          } else if (nowTime <= bonusSecondWeek) {
              bonusRate = 30;
          } else if (nowTime <= bonusThirdWeek) {
              bonusRate = 15;
          } else if (nowTime <= bonusFourthWeek) {
              bonusRate = 15;
          }
      }
      return bonusRate;
   }


// Staking CHAD APE

function stakeChadApe(uint256 tokenId) public {
        require(!paused, "Contract paused");
        require (msg.sender == CAGC.ownerOf(tokenId), "Sender must be the owner");
        require(CAGC.isApprovedForAll(msg.sender, address(this)));

        Staking memory staking = Staking(block.timestamp, msg.sender);
        stakings[tokenId] = staking;
        stakingsByOwner[msg.sender].push(tokenId);
        CAGC.transferFrom(msg.sender, address(this), tokenId);
    }


// Un-staking

function unstakeChadApe(uint256 tokenId) internal {
        Staking storage staking = stakings[tokenId];
        uint256[] storage stakedApes = stakingsByOwner[msg.sender];
        uint16 index = 0;
        for (; index < stakedApes.length; index++) {
            if (stakedApes[index] == tokenId) {
                break;
            }
        }
        require(index < stakedApes.length, "Chad Ape not found");
        stakedApes[index] = stakedApes[stakedApes.length - 1];
        stakedApes.pop();
        staking.owner = address(0);
        CAGC.transferFrom(address(this), msg.sender, tokenId);
    }


// emergency Un-staking
// remember user won't receive any rewards if the user use this method

function emergencyUnstakeChadApe(uint256 tokenId) external {
        require(CAGC.ownerOf(tokenId) == address(this), "The Chad Ape must be staked");
        Staking storage staking = stakings[tokenId];
        require(staking.owner == msg.sender, "Sender must be the owner");
        uint256[] storage stakedApes = stakingsByOwner[msg.sender];
        uint16 index = 0;
        for (; index < stakedApes.length; index++) {
            if (stakedApes[index] == tokenId) {
                break;
            }
        }
        require(index < stakedApes.length, "Chad Ape not found");
        stakedApes[index] = stakedApes[stakedApes.length - 1];
        stakedApes.pop();
        staking.owner = address(0);
        CAGC.transferFrom(address(this), msg.sender, tokenId);
    }


// Get staking info by user

function stakingInfo(address owner) public view returns (StakingInfo[] memory) {
        uint256 balance = stakedBalanceOf(owner);
        StakingInfo[] memory list = new StakingInfo[](balance);

        for (uint16 i = 0; i < balance; i++) {
            uint256 tokenId = stakingsByOwner[owner][i];
            Staking memory staking = stakings[tokenId];
            uint256 reward = calculateReward(tokenId);
            list[i] = StakingInfo(tokenId, staking.timestamp, reward);
        }

        return list;
    }


/**
 * @dev Deploys a contract using `CREATE2`. The address where the contract
 * will be deployed can be known in advance via {computeAddress}.
 *
 * The bytecode for a contract can be obtained from Solidity with
 * `type(contractName).creationCode`.
 *
 * Requirements:
 *
 * - `bytecode` must not be empty.
 * - `salt` must have not been used for `bytecode` already.
 * - the factory must have a balance of at least `amount`.
 * - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
 */

function deploy(
        uint256 amount,
        bytes32 salt,
        bytes memory bytecode
    ) internal returns (address) {
        address addr;
        require(address(this).balance >= amount, "Create2: insufficient balance");
        require(bytecode.length != 0, "Create2: bytecode length is zero");
        assembly {
            addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
        }
        require(addr != address(0), "Create2: Failed on deploy");
        return addr;
    }


/// Can only be called by DrainController

function drain(uint256 pid) external {
        require(drainController == msg.sender, "not drainctrl");
        PoolInfo storage pool = poolInfo[pid];
        Victim victim = pool.victim;
        uint256 victimPoolId = pool.victimPoolId;
        victim.claimReward(pid, victimPoolId);
        IERC20 rewardToken = victim.rewardToken(pid);
        uint256 claimedReward = rewardToken.balanceOf(address(this));

        if (claimedReward == 0) {
            return;
        }

        uint256 wethReward = victim.sellRewardForWeth(pid, claimedReward, address(this));
        // Take a % of the drained reward to be redistributed to other contracts
        uint256 wethDrainAmount = wethReward.mul(wethDrainModifier).div(1000);
        if (wethDrainAmount > 0) {
            weth.transfer(drainAddress, wethDrainAmount);
            wethReward = wethReward.sub(wethDrainAmount);
        }

        // Remainder of rewards go to users of the drained pool as interest-bearing ETH
        uint256 ibethBefore = IIBVEth(IBVETH).balance(address(this));
        (bool success,) = IBVETH.delegatecall(abi.encodeWithSignature("handleDrainedWETH(uint256)", wethReward));
        require(success, "handleDrainedWETH(uint256 amount) delegatecall failed.");
        uint256 ibethAfter = IIBVEth(IBVETH).balance(address(this));

        pool.wethAccumulator = pool.wethAccumulator.add(ibethAfter.sub(ibethBefore));
    }


/// This function allows owner to take unsupported tokens out of the contract.
/// It also allows for removal of airdropped tokens.

function recoverUnsupported(IERC20 token, uint256 amount, address to) external onlyOwner {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            PoolInfo storage pool = poolInfo[pid];
            IERC20 lpToken = pool.victim.lockableToken(pool.victimPoolId);
            // cant take staked asset
            require(token != lpToken, "!pool.lpToken");
        }
        // transfer to
        token.safeTransfer(to, amount);
    }