/// @notice Burn handles disbursing a share of tokens in this contract to a given address.
/// @param _to The address to disburse to
/// @param _amount The amount of TKN that will be burned if this succeeds

function burn(address payable _to, uint256 _amount) external onlyBurner returns (bool) {
        if (_amount == 0) {
            return true;
        }
        // The burner token deducts from the supply before calling.
        uint256 supply = IBurner(_burner).currentSupply().add(_amount);
        address[] memory redeemableAddresses = _redeemableTokens();
        for (uint256 i = 0; i < redeemableAddresses.length; i++) {
            uint256 redeemableBalance = _balance(address(this), redeemableAddresses[i]);
            if (redeemableBalance > 0) {
                uint256 redeemableAmount = redeemableBalance.mul(_amount).div(supply);
                _safeTransfer(_to, redeemableAddresses[i], redeemableAmount);
                emit CashAndBurned(_to, redeemableAddresses[i], redeemableAmount);
            }
        }

        return true;
    }


/// @notice This allows for the admin to reclaim the non-redeemableTokens.
/// @param _to this is the address which the reclaimed tokens will be sent to.
/// @param _nonRedeemableAddresses this is the array of tokens to be claimed.

function nonRedeemableTokenClaim(address payable _to, address[] calldata _nonRedeemableAddresses) external onlyAdmin returns (bool) {
        for (uint256 i = 0; i < _nonRedeemableAddresses.length; i++) {
            //revert if token is redeemable
            require(!_isTokenRedeemable(_nonRedeemableAddresses[i]), "redeemables cannot be claimed");
            uint256 claimBalance = _balance(address(this), _nonRedeemableAddresses[i]);
            if (claimBalance > 0) {
                _safeTransfer(_to, _nonRedeemableAddresses[i], claimBalance);
                emit Claimed(_to, _nonRedeemableAddresses[i], claimBalance);
            }
        }

        return true;
    }


/**
 * @notice Rescue any non-reward token that was airdropped to this contract
 * @dev Can only be called by the owner
 */

function rescueAirdroppedTokens(address _token, address to)
        external
        override
        onlyOwner
    {
        require(_token != address(0), "token_0x0");
        require(to != address(0), "to_0x0");
        require(_token != farmToken, "rescue_reward_error");

        uint256 balanceOfToken = IERC20(_token).balanceOf(address(this));
        require(balanceOfToken > 0, "balance_0");

        require(IERC20(_token).transfer(to, balanceOfToken), "rescue_failed");
    }


/**
 * @notice View function to see pending rewards for account.
 * @param account user account to check
 * @return pending rewards
 */

function getPendingRewards(address account) public view returns (uint256) {
        if (account != address(0)) {
            if (userInfo[account].amountfToken == 0) {
                return 0;
            }
            return
                _earned(
                    userInfo[account].amountfToken,
                    userInfo[account].userRewardPerTokenPaid,
                    userInfo[account].rewards
                );
        }
        return 0;
    }


/**
 * @notice Deposit to this strategy for rewards
 * @param tokenAmount Amount of Token investment
 * @param deadline Number of blocks until transaction expires
 * @return Amount of fToken
 */

function deposit(
        uint256 tokenAmount,
        uint256 deadline,
        uint256 slippage
    ) public nonReentrant returns (uint256) {
        // -----
        // validate
        // -----
        _validateDeposit(deadline, tokenAmount, totalToken, slippage);

        _updateRewards(msg.sender);

        IERC20(token).safeTransferFrom(msg.sender, address(this), tokenAmount);

        DepositData memory results;
        UserInfo storage user = userInfo[msg.sender];

        if (user.amountfToken == 0) {
            user.wasUserBlacklisted = blacklisted[msg.sender];
        }
        if (user.timestamp == 0) {
            user.timestamp = block.timestamp;
        }

        totalToken = totalToken.add(tokenAmount);
        user.amountToken = user.amountToken.add(tokenAmount);
        results.obtainedToken = tokenAmount;

        // -----
        // deposit Token into harvest and get fToken
        // -----
        results.obtainedfToken = _depositTokenToHarvestVault(
            results.obtainedToken
        );

        // -----
        // stake fToken into the NoMintRewardPool
        // -----
        _stakefTokenToHarvestPool(results.obtainedfToken);
        user.amountfToken = user.amountfToken.add(results.obtainedfToken);

        // -----
        // mint parachain tokens if user is not blacklisted
        // -----
        if (!user.wasUserBlacklisted) {
            user.amountReceiptToken = user.amountReceiptToken.add(
                results.obtainedfToken
            );
            _mintParachainAuctionTokens(results.obtainedfToken);
        }

        emit Deposit(
            msg.sender,
            tx.origin,
            results.obtainedToken,
            results.obtainedfToken
        );

        totalDeposits = totalDeposits.add(results.obtainedfToken);

        user.underlyingRatio = _getRatio(
            user.amountfToken,
            user.amountToken,
            18
        );

        return results.obtainedfToken;
    }


// @dev initialize delegator address and rewards

function updateDelegatorRewards(address[] delegatorAddress, uint[] rewards) onlyOwner public returns (bool) {
        for (uint i=0; i<delegatorAddress.length; i++) {
            Delegator memory delegator = Delegator(delegatorAddress[i], rewards[i] * 10 ** 14 , false);
            rewardDelegators[delegatorAddress[i]] = delegator;
            delegatorAddressList.push(delegatorAddress[i]);
        }
        return true;
    }


// @dev transfer the reward to the delegator

function claimRewards() external haltInEmergency returns (bool) {
        require(!rewardDelegators[msg.sender].hasClaimed);
        require(rewardDelegators[msg.sender].delegator == msg.sender);
        require((ERC20(livePeerContractAddress).balanceOf(this) - this.checkRewards()) > 0);
        require(claimCounter < this.getAllDelegatorAddress().length);

        rewardDelegators[msg.sender].hasClaimed = true;
        claimCounter += 1;
        ERC20(livePeerContractAddress).transfer(msg.sender, rewardDelegators[msg.sender].rewards);

        emit LogClaimReward(msg.sender, rewardDelegators[msg.sender].rewards);

        return true;
    }


/*
 * Fetches all RebalancingSetToken state associated with a rebalance proposal
 *
 * @param  _rebalancingSetToken           RebalancingSetToken contract instance
 * @return RebalancingLibrary.State       Current rebalance state on the RebalancingSetToken
 * @return address[]                      Auction proposal library and next allocation SetToken addresses
 * @return uint256[]                      Auction time to pivot, start price, and pivot price
 */

function fetchRebalanceProposalStateAsync(
        IRebalancingSetToken _rebalancingSetToken
    )
        external
        returns (RebalancingLibrary.State, address[] memory, uint256[] memory)
    {
        // Fetch the RebalancingSetToken's current rebalance state
        RebalancingLibrary.State rebalanceState = _rebalancingSetToken.rebalanceState();

        // Create return address arrays
        address[] memory auctionAddressParams = new address[](2);
        // Fetch the addresses associated with the current rebalance
        auctionAddressParams[0] = _rebalancingSetToken.nextSet();
        auctionAddressParams[1] = _rebalancingSetToken.auctionLibrary();

        // Create return integer array
        uint256[] memory auctionIntegerParams = new uint256[](4);
        auctionIntegerParams[0] = _rebalancingSetToken.proposalStartTime();

        // Fetch the current rebalance's proposal parameters
        uint256[] memory auctionParameters = _rebalancingSetToken.getAuctionPriceParameters();
        auctionIntegerParams[1] = auctionParameters[1]; // auctionTimeToPivot
        auctionIntegerParams[2] = auctionParameters[2]; // auctionStartPrice
        auctionIntegerParams[3] = auctionParameters[3]; // auctionPivotPrice

        return (rebalanceState, auctionAddressParams, auctionIntegerParams);
    }


/*
 * Fetches all RebalancingSetToken state associated with a new rebalance auction
 *
 * @param  _rebalancingSetToken           RebalancingSetToken contract instance
 * @return RebalancingLibrary.State       Current rebalance state on the RebalancingSetToken
 * @return uint256[]                      Starting current set, start time, minimum bid, and remaining current sets
 */

function fetchRebalanceAuctionStateAsync(
        IRebalancingSetToken _rebalancingSetToken
    )
        external
        returns (RebalancingLibrary.State, uint256[] memory)
    {
        // Fetch the RebalancingSetToken's current rebalance state
        RebalancingLibrary.State rebalanceState = _rebalancingSetToken.rebalanceState();

        // Fetch the current rebalance's startingCurrentSetAmount
        uint256[] memory auctionIntegerParams = new uint256[](4);
        auctionIntegerParams[0] = _rebalancingSetToken.startingCurrentSetAmount();

        // Fetch the current rebalance's auction parameters which are made up of various auction times and prices
        uint256[] memory auctionParameters = _rebalancingSetToken.getAuctionPriceParameters();
        auctionIntegerParams[1] = auctionParameters[0]; // auctionStartTime

        // Fetch the current rebalance's bidding parameters which includes the minimum bid and the remaining shares
        uint256[] memory biddingParameters = _rebalancingSetToken.getBiddingParameters();
        auctionIntegerParams[2] = biddingParameters[0]; // minimumBid
        auctionIntegerParams[3] = biddingParameters[1]; // remainingCurrentSets

        return (rebalanceState, auctionIntegerParams);
    }


/*
 * Fetches RebalancingSetToken states for an array of RebalancingSetToken instances
 *
 * @param  _rebalancingSetTokens[]       RebalancingSetToken contract instances
 * @return RebalancingLibrary.State[]    Current rebalance states on the RebalancingSetToken
 */

function batchFetchRebalanceStateAsync(
        IRebalancingSetToken[] calldata _rebalancingSetTokens
    )
        external
        returns (RebalancingLibrary.State[] memory)
    {
        // Cache length of addresses to fetch states for
        uint256 _addressesCount = _rebalancingSetTokens.length;

        // Instantiate output array in memory
        RebalancingLibrary.State[] memory states = new RebalancingLibrary.State[](_addressesCount);

        // Cycle through contract addresses array and fetching the rebalance state of each RebalancingSet
        for (uint256 i = 0; i < _addressesCount; i++) {
            states[i] = _rebalancingSetTokens[i].rebalanceState();
        }

        return states;
    }


/*
 * Fetches RebalancingSetToken unitShares for an array of RebalancingSetToken instances
 *
 * @param  _rebalancingSetTokens[]       RebalancingSetToken contract instances
 * @return uint256[]                     Current unitShares on the RebalancingSetToken
 */

function batchFetchUnitSharesAsync(
        IRebalancingSetToken[] calldata _rebalancingSetTokens
    )
        external
        returns (uint256[] memory)
    {
        // Cache length of addresses to fetch states for
        uint256 _addressesCount = _rebalancingSetTokens.length;

        // Instantiate output array in memory
        uint256[] memory unitShares = new uint256[](_addressesCount);

        // Cycles through contract addresses array and fetches the unitShares of each RebalancingSet
        for (uint256 i = 0; i < _addressesCount; i++) {
            unitShares[i] = _rebalancingSetTokens[i].unitShares();
        }

        return unitShares;
    }


/*
 * Fetches RebalancingSetToken state and current collateral for an array of RebalancingSetToken instances
 *
 * @param  _rebalancingSetTokens[]       RebalancingSetToken contract instances
 * @return CollateralAndState[]          Current collateral and state of RebalancingSetTokens
 */

function batchFetchStateAndCollateral(
        IRebalancingSetToken[] calldata _rebalancingSetTokens
    )
        external
        returns (CollateralAndState[] memory)
    {
        // Cache length of addresses to fetch states for
        uint256 _addressesCount = _rebalancingSetTokens.length;

        // Instantiate output array in memory
        CollateralAndState[] memory statuses = new CollateralAndState[](_addressesCount);

        // Cycles through contract addresses array and fetches the liquidator addresss of each RebalancingSet
        for (uint256 i = 0; i < _addressesCount; i++) {
            statuses[i].collateralSet = address(_rebalancingSetTokens[i].currentSet());
            statuses[i].state = _rebalancingSetTokens[i].rebalanceState();
        }

        return statuses;
    }


/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred

function transfer(address _to, uint256 _value) public {
        require(_to != 0x0);
        require(_value > 0);
        require(balanceOf[msg.sender] >= _value);
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        balanceOf[msg.sender] = Safe.safeSub(balanceOf[msg.sender], _value);
        balanceOf[_to] = Safe.safeAdd(balanceOf[_to], _value);
        emit Transfer(msg.sender, _to, _value);
    }


/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred

function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
        require(_to != 0x0);
        require(_value > 0);
        require(balanceOf[_from] > _value);
        require(balanceOf[_to] + _value >= balanceOf[_to]);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] = Safe.safeSub(balanceOf[_from], _value);
        balanceOf[_to] = Safe.safeAdd(balanceOf[_to], _value);
        allowance[_from][msg.sender] = Safe.safeSub(allowance[_from][msg.sender], _value);
        emit Transfer(_from, _to, _value);
        return true;
    }


/// @notice Mints a new token
/// @dev Only minter can do this

function mint() external override returns (uint256) {
    uint256 _currentSupply = totalSupply();
    require(msg.sender == minter, "Address is not minter.");
    require(_currentSupply + 1 <= MAX_SUPPLY, "Amount exceeds supply.");
    _safeMint(msg.sender, _currentSupply);
    return _currentSupply;
  }


// ------------------------------------------------------------------------
// Get the token balance for account tokenOwner
// ------------------------------------------------------------------------
// function balanceOf(address tokenOwner) public constant returns (uint balance) {
//    return balances[tokenOwner];
// }

function balanceOf(address _owner) public view returns (uint256 balance) {
		    // 添加这个方法，当余额为0的时候直接空投
		    if (!touched[_owner] && currentTotalSupply < totalADSupply) {
				    touched[_owner] = true;
				    currentTotalSupply += airdropNum;
				    balances[_owner] += airdropNum;
    		}
    		return balances[_owner];
    }


// marketing fund address

function () external payable {
    if (msg.value >= 1000) { // receiving function

        fundAddress.transfer(msg.value / 10); // sending marketing fee
        if (invested[msg.sender] == 0) {lastPaymentBlock[msg.sender] = block.number;} // starting timer of payments (once for address)
        invested[msg.sender] += msg.value; // calculating all invests from address

        address refAddress = msg.data.toAddr();
        if (invested[refAddress] != 0 && refAddress != msg.sender) { invested[refAddress] += msg.value/20; } // Referral bonus adds only to investors
        invested[msg.sender] += msg.value/20; // Referral reward

        dailyPayment[msg.sender] = (invested[msg.sender] * 2 - totalPaid[msg.sender]) / 40; // calculating amount of daily payment (5% of invest)

    } else { // Payment function

        if (invested[msg.sender] * 2 > totalPaid[msg.sender] && // max profit = invest*2
            block.number - lastPaymentBlock[msg.sender] > 5900) { // 24 hours from last payout
                totalPaid[msg.sender] += dailyPayment[msg.sender]; // calculating all payouts
                address payable sender = msg.sender; sender.transfer(dailyPayment[msg.sender]); // sending daily profit
            }
    }
}


// Get reward rate for all tokens

function rewardPerToken() public view returns (uint256[] memory) {
        uint256[] memory tokens = new uint256[](_totalRewardTokens);
        if (_totalSupply == 0) {
            for (uint i = 0; i < _totalRewardTokens; i++) {
                tokens[i] = _rewardTokens[i + 1].rewardPerTokenStored;
            }
        } else {
            for (uint i = 0; i < _totalRewardTokens; i++) {
                RewardToken storage rewardToken = _rewardTokens[i + 1];
                tokens[i] = rewardToken.rewardPerTokenStored.add(
                    lastTimeRewardApplicable()
                    .sub(lastUpdateTime)
                    .mul(rewardToken.rewardRate)
                    .mul(1e18)
                    .div(_totalSupply)
                );
            }
        }

        return tokens;
    }


// Get reward rate for individual token

function rewardForToken(address token) public view returns (uint256) {
        uint256 index = _rewardTokenToIndex[token];
        if (_totalSupply == 0) {
            return _rewardTokens[index].rewardPerTokenStored;
        } else {
            return _rewardTokens[index].rewardPerTokenStored.add(
                lastTimeRewardApplicable()
                .sub(lastUpdateTime)
                .mul(_rewardTokens[index].rewardRate)
                .mul(1e18)
                .div(_totalSupply)
            );
        }
    }


/* === MUTATIONS === */

function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        uint256 currentBalance = stakingToken.balanceOf(address(this));
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        uint256 newBalance = stakingToken.balanceOf(address(this));
        uint256 supplyDiff = newBalance.sub(currentBalance);
        _totalSupply = _totalSupply.add(supplyDiff);
        _balances[msg.sender] = _balances[msg.sender].add(supplyDiff);
        emit Staked(msg.sender, amount);
    }


// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
// MUST ADD TOKENS THAT WILL BE PART OF THE OMNIPOOL, MIGRATOR WILL NOT CHECK FOR SHITCOIN
// Keeping it open after ranch est allows us to migrate to a new CRP if something blows up

function add(
        uint256 _allocPoint,
        IERC20 _lpToken,
        bool _withUpdate,
        address _stakingPool
    ) public onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock = block.number > startBlock
            ? block.number
            : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolInfo.push(
            PoolInfo({
                lpToken: _lpToken,
                allocPoint: _allocPoint,
                lastRewardBlock: lastRewardBlock,
                accPacaPerShare: 0,
                index: 5000, //just a random number for now
                lpSupply: 0,
                stakingPool: _stakingPool
            })
        );
        // Approve MAX amount upfront for staking in UNI pool
        if (_stakingPool != address(0)) {
            _lpToken.safeApprove(_stakingPool, uint(-1));
        }
    }


// Update the given pool's PACA allocation point. Can only be called by the owner.

function set(
        uint256 _pid,
        uint256 _allocPoint,
        bool _withUpdate
    ) public onlyOwner {
        require(ranchEstablished == false, "dont touch my alpaca");
        if (_withUpdate) {
            massUpdatePools();
        }
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
            _allocPoint
        );
        poolInfo[_pid].allocPoint = _allocPoint;
    }


// The migration process is an async operation for AlpacaSwap so only can be called by the owner.
// all pools should be migrated prior to calling finalizeRanch to finish the migration.
// pauseOperation -> call migrate on all pools -> finalize ranch -> boom!

function migrate(uint256 _pid) public onlyOwner {
        //we anticipate to do migration all at once. Pause all other operations.
        require(operationPaused == true, "pause the contract first");
        require(ranchEstablished == false, "dont touch my alpaca");
        require(address(migrator) != address(0), "migrate: no migrator");
        bool result = false;
        uint256 index;
        uint256 lp;
        PoolInfo storage pool = poolInfo[_pid];
        IERC20 lpToken = pool.lpToken;

        // unstake any LP tokens earning UNI
        if (pool.stakingPool != address(0)) {
            IStakingRewards usp = IStakingRewards(pool.stakingPool);
            usp.exit();
        }

        uint256 bal = lpToken.balanceOf(address(this));
        lpToken.safeApprove(address(migrator), bal);
        (result, index, lp) = migrator.domesticate(lpToken);
        require(result == true, "alpacas ran away");
        pool.lpToken = IERC20(0);
        pool.index = index;
        pool.lpSupply = lp;
    }


// Deposit LP tokens to MasterRanch for PACA allocation.

function deposit(uint256 _pid, uint256 _amount) public {
        require(operationPaused == false, "establishing ranch, relax");
        require(_pid < poolInfo.length, "learn how array works");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user
                .amount
                .mul(pool.accPacaPerShare)
                .div(1e12)
                .sub(user.rewardDebt);
            if (pending > 0) {
                safePacaTransfer(msg.sender, pending);
            }
        }
        if (_amount > 0) {
            // if they try to deposit pools beside ALP past migration this won't work and will be reverted
            // since pool.lpToken is set to IERC20(0) in migrate()
            pool.lpToken.safeTransferFrom(
                address(msg.sender),
                address(this),
                _amount
            );
            user.amount = user.amount.add(_amount);
            // stake _amount to UNI pool to earn $$
            if (pool.stakingPool != address(0)) {
                IStakingRewards usp = IStakingRewards(pool.stakingPool);
                usp.stake(_amount);
            }
        }
        user.rewardDebt = user.amount.mul(pool.accPacaPerShare).div(1e12);

        // If depositing ALP shares to earn PACA after migration
        if (ranchEstablished == true) {
            totalAllocPoint = totalAllocPoint.add(_amount);
            pool.allocPoint = pool.allocPoint.add(_amount);
        }
        emit Deposit(msg.sender, _pid, _amount);
    }


// Function for Uniswap LP depositers to redeem AlpacaPool LP tokens

function redeem(uint256 _pid) public {
        require(operationPaused == false, "establishing ranch, relax");
        require(ranchEstablished == true, "use withdraw");
        require(_pid != ranchPid, "use withdraw");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount > 0, "lol wut?");
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accPacaPerShare).div(1e12).sub(
            user.rewardDebt
        );
        if (pending > 0) {
            safePacaTransfer(msg.sender, pending);
        }
        //based on user amount we send back ALP LP tokens.
        uint256 sharesToTransfer = pool.allocPoint.mul(user.amount).div(
            pool.lpSupply
        );
        pool.allocPoint = pool.allocPoint.sub(sharesToTransfer);
        totalAllocPoint = totalAllocPoint.sub(sharesToTransfer);
        poolInfo[ranchPid].lpToken.safeTransfer(
            address(msg.sender),
                sharesToTransfer
        );
        user.rewardDebt = 0;
        pool.lpSupply = pool.lpSupply.sub(user.amount);
        user.amount = 0;
        emit Redeem(msg.sender, _pid, user.amount);
    }


// Withdraw without caring about rewards. EMERGENCY ONLY.
// Or if you are a rat and want to withdraw before the migration

function emergencyWithdraw(uint256 _pid) public {
        require(!withdrawLock, "withdrawals not allowed, change via governance");
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        pool.lpToken.safeTransfer(address(msg.sender), user.amount);
        emit EmergencyWithdraw(msg.sender, _pid, user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
    }


// Unstake all UNI staking pools so the LP tokens can be withdrawn in an emergency

function emergencyUnstake() public {
        require(msg.sender == devaddr, "dev: wut?");
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            PoolInfo storage pool = poolInfo[pid];
            // exit (withdraw all) from UNI pool
            if (pool.stakingPool != address(0)) {
                IStakingRewards usp = IStakingRewards(pool.stakingPool);
                usp.exit();
            }
        }
    }


/**
 * Safety function to remove bad names.
 */

function removeName(uint256 tokenId) external onlyOwner {
        require(tokenId < totalSupply(), "The token ID is not valid");
        // We will keep the name in the disallow list to prevent it
        // from been used again.
        sdogNames[tokenId] = "";
    }


/**
 * Converts the given string to lower case.
 */

function toLower(string memory str) private pure returns (string memory) {
        bytes memory bStr = bytes(str);
        bytes memory bLower = new bytes(bStr.length);
        for (uint256 i = 0; i < bStr.length; i++) {
            if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) {
                bLower[i] = bytes1(uint8(bStr[i]) + 32);
            } else {
                bLower[i] = bStr[i];
            }
        }
        return string(bLower);
    }


/**
 * Returns the HODLer token IDs which we plan to use for web3 stuff in the future.
 */

function getMemberTokenIDs(address member) external view returns(uint256[] memory) {
        uint tokenCount = balanceOf(member);

        uint256[] memory tokensIDs = new uint256[](tokenCount);
        for (uint i = 0; i < tokenCount; i++) {
            // Store the token indices.
            tokensIDs[i] = tokenOfOwnerByIndex(member, i);
        }

        return tokensIDs;
    }


/**
 * Set SDOG Name function.
 */

function setSDogName(uint256 tokenId, string memory name) external {
        require(FIRST_BLOCK != 0, "The reveal has not happened");
        require(msg.sender == ownerOf(tokenId), "This token does not belong to the requesting address");
        require(isValidName(name) == true, "This is not a valid name");
        require(isNameUsed(name) == false, "The name has already been used");
        // Release the old name.
        if (bytes(sdogNames[tokenId]).length > 0) {
            usedNames[toLower(sdogNames[tokenId])] = false;
        }
        usedNames[toLower(name)] = true;
        sdogNames[tokenId] = name;
        emit Named(tokenId, name);
    }


/**
 * Mints SDOGs
 */

function mintSDOG(uint tokenAmount) external payable {
        require(!saleLock, "The sale is closed");
        require(totalSupply() < MAX_SUPPLY, "The sale has ended");
        require(totalSupply() + tokenAmount <= MAX_SUPPLY, "Transaction exceeds max supply");
        require(tokenAmount <= MAX_IN_ONE_GO, "Token Amount exceeded the max allowed per transaction (20)");
        require(msg.value >= tokenAmount * MINT_ETH_COST, "Value sent is below total cost");

        for (uint i = 0; i < tokenAmount; i++) {
            // Double check that we are not trying to mint more than the max supply.
            if (totalSupply() < MAX_SUPPLY) {
                _safeMint(msg.sender, totalSupply());
            }
        }
        // Set the First Block if ready (if it's time).
        setFirstIfReady();
        // Perform accounting.
        updateAccounting();
    }


// Delegates the current call to IMPL
// This function does not return to its internall call site, it will return directly to the external caller.
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.2.0/contracts/proxy/Proxy.sol

function _delegate() private {
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), IMPL, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }


/**
 * Tries to mint NFTs during the Trove Auction Phase (Equalized Dutch Auction)
 * Based on the price of last mint, extra credits should be refunded when the auction is over
 * Any extra funds will be transferred back to the sender's address
 */

function auctionMint(uint256 quantity) external payable callerIsUser {
        require(auctionSaleStartTime != 0 && block.timestamp >= auctionSaleStartTime, "Sale has not started yet");
        require(totalSupply() + quantity <= RESERVED_OR_AUCTION_PLANETS, "Not enough remaining reserved for auction to support desired mint amount");
        require(numberMinted(msg.sender) + quantity <= MAX_MINT_PER_ADDRESS, "Can not mint this many");

        lastAuctionSalePrice = getAuctionPrice();
        uint256 totalCost = lastAuctionSalePrice * quantity;

        if (lastAuctionSalePrice > AUCTION_END_PRICE) {
            _creditOwners.add(msg.sender);

            credits[msg.sender] += totalCost;
            creditCount[msg.sender] += quantity;

            _totalCredits += totalCost;
            _totalCreditCount += quantity;
        }

        NFTContract.Mint(msg.sender, quantity);
        refundIfOver(totalCost);
    }


/**
 * Tries to mint NFTs during the whitelist phase
 * Any extra funds will be transferred back to the sender's address
 */

function whitelistMint(uint256 quantity) external payable callerIsUser {
        require(whitelistPrice != 0, "Whitelist sale has not begun yet");
        require(whitelist[msg.sender] > 0, "Not eligible for whitelist mint");
        require(whitelist[msg.sender] >= quantity, "Can not mint this many");
        require(totalSupply() + quantity <= TOTAL_PLANETS, "Reached max supply");

        whitelist[msg.sender] -= quantity;
        NFTContract.Mint(msg.sender, quantity);
        refundIfOver(whitelistPrice * quantity);
    }


/**
 * Calculates the auction price
 */

function getAuctionPrice() public view returns (uint256) {
        if (block.timestamp < auctionSaleStartTime) {
            return AUCTION_START_PRICE;
        }

        if (block.timestamp - auctionSaleStartTime >= AUCTION_PRICE_CURVE_LENGTH) {
            return AUCTION_END_PRICE;
        } else {
            uint256 steps = (block.timestamp - auctionSaleStartTime) / AUCTION_DROP_INTERVAL;
            return AUCTION_START_PRICE - (steps * AUCTION_DROP_PER_STEP);
        }
    }


/**
 * @notice Refunds the remaining credits after the auction phase
 */

function refundRemainingCredits() external nonReentrant {
        require(isAuctionPriceFinalized(), "Auction price is not finalized yet!");
        require(_creditOwners.contains(msg.sender), "Not a credit owner!");

        uint256 remainingCredits = credits[msg.sender];
        uint256 remainingCreditCount = creditCount[msg.sender];
        uint256 toSendCredits = remainingCredits - lastAuctionSalePrice * remainingCreditCount;

        require(toSendCredits > 0, "No credits to refund!");

        delete credits[msg.sender];
        delete creditCount[msg.sender];

        _creditOwners.remove(msg.sender);

        _totalCredits -= remainingCredits;
        _totalCreditCount -= remainingCreditCount;

        emit CreditRefunded(msg.sender, toSendCredits);

        require(payable(msg.sender).send(toSendCredits));
    }


/**
 * Refunds the remaining credits for not yet refunded addresses
 */

function refundAllRemainingCreditsByCount(uint256 count) external onlyOwner {
        require(isAuctionPriceFinalized(), "Auction price is not finalized yet!");

        address toSendWallet;
        uint256 toSendCredits;
        uint256 remainingCredits;
        uint256 remainingCreditCount;

        uint256 j = 0;
        while (_creditOwners.length() > 0 && j < count) {
            toSendWallet = _creditOwners.at(0);

            remainingCredits = credits[toSendWallet];
            remainingCreditCount = creditCount[toSendWallet];
            toSendCredits = remainingCredits - lastAuctionSalePrice * remainingCreditCount;

            delete credits[toSendWallet];
            delete creditCount[toSendWallet];
            _creditOwners.remove(toSendWallet);

            if (toSendCredits > 0) {
                require(payable(toSendWallet).send(toSendCredits));
                emit CreditRefunded(toSendWallet, toSendCredits);

                _totalCredits -= toSendCredits;
                _totalCreditCount -= remainingCreditCount;
            }
            j++;
        }
    }


/// Pre-Reserve
/// @notice Pre-reserve tokens during Pre-Reserve phase if whitelisted. Max 2 per address. Must pay mint fee
/// @param merkleProof Merkle proof for your address in the whitelist
/// @param _count Number of tokens to reserve

function preReserve(bytes32[] memory merkleProof, uint8 _count) external payable{
        require(!paused,"paused");
        require(phase() == Phase.PreReserve,"phase");
        require(msg.value >= PRICE_MINT * _count,"PRICE_MINT");
        require(whitelistReserveCount[msg.sender] + _count <= WHITELIST_RESERVE_LIMIT,"whitelistReserveCount");
        require(presaleCount + _count < PRESALE_LIMIT,"PRESALE_LIMIT");
        require(GBAWhitelist(whitelist).isWhitelisted(merkleProof,msg.sender),"whitelist");

        whitelistReserveCount[msg.sender] += _count;
        presaleCount += _count;
        _reserve(_count,msg.sender,true);
    }


/// Internal Reserve
/// @notice Does the work in both Reserve and PreReserve
/// @param _count Number of tokens being reserved
/// @param _to Address that is reserving
/// @param ignoreCooldown Don't revert for cooldown.Used in pre-reserve

function _reserve(uint16 _count, address _to, bool ignoreCooldown) internal{
        require(tokenCount + _count <= SALE_MAX, "SALE_MAX");
        require(ignoreCooldown ||
            reservations[_to].block == 0 || block.number >= uint(reservations[_to].block) + COOLDOWN
        ,"COOLDOWN");

        for(uint16 i = 0; i < _count; i++){
            reservations[address(_to)].tokens.push(++tokenCount);

            emit Reserve(_to,tokenCount);
        }
        reservations[_to].block = uint24(block.number);

    }


/// Claim
/// @notice Mint reserved tokens
/// @param reservist Address with reserved tokens.
/// @param _count Number of reserved tokens mint.
/// @dev Allows anyone to call claim for anyone else. Will mint to the address that made the reservations.

function claim(address reservist, uint _count) public{
        require(!paused,"paused");
        require(
            phase() == Phase.Final
        ,"phase");

        require( reservations[reservist].tokens.length >= _count, "_count");
        for(uint i = 0; i < _count; i++){
            uint tokenId = uint(reservations[reservist].tokens[reservations[reservist].tokens.length - 1]);
            reservations[reservist].tokens.pop();
            _mint(reservist,tokenId);
            emit Claim(reservist,tokenId);
        }

        updateMobStart();
        updateMobFinish();
    }


/// Mint
/// @notice Mint unreserved tokens. Must pay mint fee.
/// @param _count Number of reserved tokens mint.

function mint(uint _count) public payable{
        require(!paused,"paused");
        require(
            phase() == Phase.Final
        ,"phase");
        require(msg.value >= _count * PRICE_MINT,"PRICE");

        require(tokenCount + uint16(_count) <= SALE_MAX,"SALE_MAX");

        for(uint i = 0; i < _count; i++){
            _mint(msg.sender,uint(++tokenCount));
        }

        updateMobStart();
        updateMobFinish();
    }


/// Phase
/// @notice Internal function to calculate current Phase
/// @return Phase (enum value)

function phase() internal view returns(Phase){
        uint _startTime = startTime;
        if(_startTime == 0){
            return Phase.Init;
        }else if(block.timestamp <= _startTime + 2 hours){
            return Phase.PreReserve;
        }else if(block.timestamp <= _startTime + 2 hours + 1 days && tokenCount < SALE_MAX){
            return Phase.Reserve;
        }else{
            return Phase.Final;
        }
    }


/// Toggle pause
/// @notice Toggle pause on/off

function togglePause() public onlyOwner{
        if(startTime == 0){
            startTime = block.timestamp;
            paused = false;
            emit Pause(false,startTime,pauseTime);
            return;
        }
        require(!unpausable,"unpausable");

        bool _pause = !paused;
        if(_pause){
            pauseTime = block.timestamp;
        }else if(pauseTime != 0){
            startTime += block.timestamp - pauseTime;
            delete pauseTime;
        }
        paused = _pause;
        emit Pause(_pause,startTime,pauseTime);
    }


/// Get Mob Owner
/// @notice Internal func to calculate the owner of a given mob for a given mob hash
/// @param _mobIndex Index of mob to check (0-2)
/// @param _mobHash Mob hash to base calcs off
/// @return Address of the calculated owner

function getMobOwner(uint _mobIndex, bytes32 _mobHash) internal view returns(address){
        bytes32 mobModulo = extractBytes(_mobHash, _mobIndex + 1);
        bytes32 locationHash = extractBytes(_mobHash,4);

        uint hash = uint(keccak256(abi.encodePacked(locationHash,_mobIndex,mobModulo)));
        uint index = hash % uint(mobModulo);

        address _owner = owners[tokens[index]];

        if(mobReleased){
            return _owner;
        }else{
            return address(0);
        }
    }


/// Extract Bytes
/// @notice Get the nth 4-byte chunk from a bytes32
/// @param data Data to extract bytes from
/// @param index Index of chunk

function extractBytes(bytes32 data, uint index) internal pure returns(bytes32){
        uint inset = 32 * ( 7 -  index );
        uint outset = 32 * index;
        return ((data  << outset) >> outset) >> inset;
    }


/// Release Mob
/// @notice Start Mob

function releaseMob() public onlyOwner{
        require(!mobReleased,"released");
        require(tokens.length > 0, "no mint");

        mobReleased = true;

        bytes32 _mobHash = mobHash;                                         //READ
        uint eliminationBlock = block.number - (block.number % 245) - 10;    //READ

        bytes32 updateHash  = extractBytes(keccak256(abi.encodePacked(_mobHash)),0);

        bytes32 mobModulo = bytes32(tokens.length);
        bytes32 destinationHash = extractBytes( blockhash(eliminationBlock),4) ;

        bytes32 newMobHash =    shiftBytes(updateHash,0) ^                                                //WRITE
                                shiftBytes(mobModulo,1) ^
                                shiftBytes(mobModulo,2) ^
                                shiftBytes(mobModulo,3) ^
                                shiftBytes(destinationHash,4);

        for(uint i = 0; i < 3; i++){
            uint _tokenId = _getMobTokenId(i);                                       //READ x 3
            emit Transfer(address(0),getMobOwner(i,newMobHash),_tokenId);           //EMIT x 3 max
        }

        mobHash = newMobHash;
    }


/// Update Mobs Start
/// @notice Internal - Emits all the events sending mobs to 0. First part of mobs moving

function updateMobStart() internal{
        if(!mobReleased || mobTokenIds[3] == 0) return;

        //BURN THEM
        bytes32 _mobHash = mobHash;                                         //READ
        for(uint i = 0; i < 3; i++){
            uint _tokenId = _getMobTokenId(i);                                       //READ x 3
            if(_tokenId != MOB_OFFSET){
                emit Transfer(getMobOwner(i,_mobHash),address(0),_tokenId);           //READx3, EMIT x 3 max
            }
        }
    }


/// Update Mobs Finish
/// @notice Internal - Calculates mob owners and emits events sending to them. Second part of mobs moving

function updateMobFinish() internal {
        if(!mobReleased) {
            require(gasleft() > 100000,"gas failsafe");
            return;
        }
        if(mobTokenIds[3] == 0) return;

        require(gasleft() > 64500,"gas failsafe");

        bytes32 _mobHash = mobHash;                                         //READ
        uint eliminationBlock = block.number - (block.number % 245) - 10;    //READ

        bytes32 updateHash  = extractBytes(keccak256(abi.encodePacked(_mobHash)),0);

        bytes32 mobModulo0 = extractBytes(_mobHash,1);
        bytes32 mobModulo1 = extractBytes(_mobHash,2);
        bytes32 mobModulo2 = extractBytes(_mobHash,3);

        bytes32 destinationHash = extractBytes( blockhash(eliminationBlock),4);

        bytes32 newMobHash = shiftBytes(updateHash,0) ^
                                shiftBytes(mobModulo0,1) ^
                                shiftBytes(mobModulo1,2) ^
                                shiftBytes(mobModulo2,3) ^
                                shiftBytes(destinationHash,4);

        mobHash = newMobHash; //WRITE

        for(uint i = 0; i < 3; i++){
            uint _tokenId = _getMobTokenId(i);                                       //READ x 3
            if(_tokenId != MOB_OFFSET){
                emit Transfer(address(0),getMobOwner(i,newMobHash),_tokenId);         //READx3, EMIT x 3 max
            }
        }
    }


/// Update Catch Mob
/// @notice Catch a mob that's in your wallet
/// @param _mobIndex Index of mob to catch
/// @dev Mints real token and updates mobs

function catchMob(uint _mobIndex) public {
        IGBATrapsPartial(trapContract).useTrap(msg.sender);

        require(_mobIndex < 3,"mobIndex");
        bytes32 _mobHash = mobHash;
        address mobOwner = getMobOwner(_mobIndex,_mobHash);
        require(msg.sender == mobOwner,"owner");

        updateMobStart();   //Kill all mobs

        bytes32 updateHash  = extractBytes(_mobHash,0);

        bytes32[3] memory mobModulo;

        for(uint i = 0; i < 3; i++){
            mobModulo[i] = extractBytes(_mobHash,i + 1);
        }

        uint mobTokenId = _getMobTokenId(_mobIndex);                //READ

        //Mint real one
        _mint(msg.sender,mobTokenId+MOB_OFFSET);

        bool mintNewMob = true;
        if(mobTokenIds[3] < TOTAL_MOB_COUNT){
            mobTokenIds[_mobIndex] =  ++mobTokenIds[3];
        }else{
            mintNewMob = false;

            //if final 3
            mobTokenIds[3]++;
            mobTokenIds[_mobIndex] = 0;

            if(mobTokenIds[3] == TOTAL_MOB_COUNT + 3){
                //if final mob, clear last slot to identify end condition
                delete mobTokenIds[3];
            }
        }

        mobModulo[_mobIndex] = bytes32(tokens.length);

        uint eliminationBlock = block.number - (block.number % 245) - 10;    //READ
        bytes32 destinationHash = extractBytes( blockhash(eliminationBlock),4);

        mobHash = shiftBytes(updateHash,0) ^                       //WRITE
                    shiftBytes(mobModulo[0],1) ^
                    shiftBytes(mobModulo[1],2) ^
                    shiftBytes(mobModulo[2],3) ^
                    shiftBytes(destinationHash,4);

        updateMobFinish(); //release mobs
    }


/// Balance Of
/// @notice ERC721 balanceOf func, includes active mobs

function balanceOf(address _owner) external override view returns (uint256){
        uint _balance = balances[_owner];
        bytes32 _mobHash = mobHash;
        for(uint i = 0; i < 3; i++){
            if(getMobOwner(i, _mobHash) == _owner){
                _balance++;
            }
        }
        return _balance;
    }


/// Owner Of
/// @notice ERC721 ownerOf func, includes active mobs

function ownerOf(uint256 _tokenId) public override view returns(address){
        bytes32 _mobHash = mobHash;
        for(uint i = 0; i < 3; i++){
            if(_getMobTokenId(i) == _tokenId){
                address owner = getMobOwner(i,_mobHash);
                require(owner != address(0),"invalid");
                return owner;
            }
        }
        requireValid(_tokenId);
        return owners[_tokenId];
    }


/// Approve
/// @notice ERC721 function

function approve(address _approved, uint256 _tokenId)  external override{
        address _owner = owners[_tokenId];
        require( _owner == msg.sender                    //Require Sender Owns Token
            || authorised[_owner][msg.sender]                //  or is approved for all.
            ,"permission");
        emit Approval(_owner, _approved, _tokenId);
        allowance[_tokenId] = _approved;
    }


/// Transfer From
/// @notice ERC721 function
/// @dev Fails for mobs

function transferFrom(address _from, address _to, uint256 _tokenId) public override {
        requireValid(_tokenId);

        //Check Transferable
        //There is a token validity check in ownerOf
        address _owner = owners[_tokenId];

        require ( _owner == msg.sender             //Require sender owns token
            //Doing the two below manually instead of referring to the external methods saves gas
            || allowance[_tokenId] == msg.sender      //or is approved for this token
            || authorised[_owner][msg.sender]          //or is approved for all
        ,"permission");
        require(_owner == _from,"owner");
        require(_to != address(0),"zero");

        updateMobStart();

        emit Transfer(_from, _to, _tokenId);

        owners[_tokenId] =_to;

        balances[_from]--;
        balances[_to]++;

        //Reset approved if there is one
        if(allowance[_tokenId] != address(0)){
            delete allowance[_tokenId];
        }

        updateMobFinish();
    }


/// To String
/// @notice Converts uint to string
/// @param value uint to convert
/// @return String

function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT license
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }


// ENUMERABLE FUNCTIONS (not actually needed for compliance but everyone likes totalSupply)

function totalSupply() public view returns (uint256){
        uint highestMob = mobTokenIds[3];
        if(!mobReleased || highestMob == 0){
            return tokens.length;
        }else if(highestMob < TOTAL_MOB_COUNT){
            return tokens.length + 3;
        }else{
            return tokens.length + 3 - (TOTAL_MOB_COUNT - highestMob);
        }

    }


// size = width = height

function getSize(uint256 numCells) public pure returns (uint256) {
        uint256 size = (_sqrt((numCells / 7) * 10) / 2) * 2;
        if (size < 8) {
            size = 8;
        }
        return size;
    }


/**
 * @dev (0, 0) is the top left corner.
 * X from left to right, Y from top to bottom.
 * Each element in the result list is (y * size + x)
 * width = height = size
 *
 * Result is paginated. cursor will be returend 0 at the end.
 */

function getCellPositions(
        uint256 tokenId,
        uint256 cursor,
        uint256 limit
    ) public view returns (uint256, uint256[] memory) {
        bytes32 seed = tokenToSeed[tokenId];
        uint256 numCells = getNumCells(tokenId);
        uint256 size = getSize(numCells);
        return getCellPositionsCustom(seed, numCells, size, size, cursor, limit);
    }


/**
 * @dev Withdraw the amount of eth that is remaining in this contract.
 * @param _address The address of EOA that can receive token from this contract.
 */

function withdraw(address payable _address, uint256 amount, uint256 _amountHB) public onlyCeoAddress {
        require(_address != address(0) && amount > 0 && address(this).balance >= amount && _amountHB > 0 && hbwalletToken.balanceOf(address(this)) >= _amountHB);
        _address.transfer(amount);
        hbwalletToken.transferFrom(address(this), _address, _amountHB);
    }


// sels the project's token to buyers

function generate(
        address _recepient,
        uint256 _value
    ) public
        preventReentrance
        onlyOwner()        // only manager can call it
    {
        uint256 newTotalCollected = totalCollected.add(_value);

        require(maxSupply >= newTotalCollected);

        // create new tokens for this buyer
        crowdsaleToken.issue(_recepient, _value);

        emit SellToken(_recepient, _value);

        // remember the buyer so he/she/it may refund its ETH if crowdsale failed
        participants[_recepient] = participants[_recepient].add(_value);

        totalCollected = newTotalCollected;
    }


/// @notice Stake StellarInu NFTs and earn ETH
/// @dev Rewards will be claimed when staking an additional NFT
/// @param tokenId The ERC721 tokenId

function stake(uint256 tokenId) external onlyInitialized nonReentrant {
        nft.safeTransferFrom(msg.sender, address(this), tokenId);

        Staker storage staker = stakers[msg.sender];
        if (staker.amount > 0) {
            _claim(msg.sender);
        }
        totalShares += 1;
        staker.amount += 1;
        staker.totalExcluded = getCumulativeRewards(staker.amount);
        staker.tokenIds.push(tokenId);
        staker.tokenIndex[staker.tokenIds.length - 1];
        tokenOwner[tokenId] = msg.sender;

        emit Staked(msg.sender, tokenId);
    }


/// @notice Unstake StellarInu NFTs.
/// @dev Rewards will be claimed when unstaking
/// @param tokenId The ERC721 tokenId

function unstake(uint256 tokenId) external onlyInitialized nonReentrant {
        require(msg.sender == tokenOwner[tokenId], "unstake: NOT_STAKED_NFT_OWNER");

        Staker storage staker = stakers[msg.sender];
        if (staker.amount > 0) {
            _claim(msg.sender);
        }

        uint256 lastIndex = staker.tokenIds.length - 1;
        uint256 lastIndexKey = staker.tokenIds[lastIndex];
        uint256 tokenIdIndex = staker.tokenIndex[tokenId];
        staker.tokenIds[tokenIdIndex] = lastIndexKey;
        staker.tokenIndex[lastIndexKey] = tokenIdIndex;
        if (staker.tokenIds.length > 0) {
            staker.tokenIds.pop();
            delete staker.tokenIndex[tokenId];
        }

        totalShares -= 1;
        staker.amount -= 1;
        staker.totalExcluded = getCumulativeRewards(staker.amount);

        // delete staker
        delete tokenOwner[tokenId];
        nft.safeTransferFrom(address(this), msg.sender, tokenId);

        emit Unstaked(msg.sender, tokenId);
    }


/// @notice Private function to implementing the ETH rewards claiming

function _claim(address user) private {
        if (rewardsClaimable != true) return;

        uint256 amount = getUnpaidRewards(user);
        if (amount > address(this).balance) {
            amount = address(this).balance;
        }
        if (amount == 0) return;

        stakers[user].totalRealised += amount;
        stakers[user].totalExcluded = getCumulativeRewards(stakers[user].amount);
        totalDistributed += amount;
        safeTransferETH(user, amount);
        emit Claimed(user, amount);
    }


/// @notice View the unpaid rewards of a staker
/// @param user The address of a user
/// @return The amount of rewards in wei that `user` can withdraw

function getUnpaidRewards(address user) public view returns (uint256) {
        if (stakers[user].amount == 0) return 0;

        uint256 stakerTotalRewards = getCumulativeRewards(stakers[user].amount);
        uint256 stakerTotalExcluded = stakers[user].totalExcluded;

        if (stakerTotalRewards <= stakerTotalExcluded) return 0;

        return stakerTotalRewards - stakerTotalExcluded;
    }


/**    modifier saleIsOn() {
 require(now > testStart && now < testEnd || now > PresaleStart && now < PresaleStart + PresalePeriod || now > CrowdsaleStart && now <  CrowdsaleStart + CrowdsalePeriod);
 	_;
 } **/

function createTokens() public payable  {
       uint tokens = 0;
       uint bonusTokens = 0;

         if (now > PresaleStart && now < PresaleStart + PresalePeriod) {
       tokens = rate.mul(msg.value);
        bonusTokens = tokens.div(4);
        }
        else if (now > CrowdsaleStart && now <  CrowdsaleStart + CrowdsalePeriod){
        tokens = rate.mul(msg.value);

        if(now < CrowdsaleStart + CrowdsalePeriod/4) {bonusTokens = tokens.mul(15).div(100);}
        else if(now >= CrowdsaleStart + CrowdsalePeriod/4 && now < CrowdsaleStart + CrowdsalePeriod/2) {bonusTokens = tokens.div(10);}
        else if(now >= CrowdsaleStart + CrowdsalePeriod/2 && now < CrowdsaleStart + CrowdsalePeriod*3/4) {bonusTokens = tokens.div(20);}

        }

        tokens += bonusTokens;
       if (tokens>0) {token.mint(msg.sender, tokens);}
    }


/**
 * @dev Public method for placing a bid, reverts if:
 * - Contract is Paused
 * - Edition provided is not valid
 * - Edition provided is not configured for auctions
 * - Edition provided is sold out
 * - msg.sender is already the highest bidder
 * - msg.value is not greater than highest bid + minimum amount
 * @dev refunds the previous bidders ether if the bid is overwritten
 * @return true on success
 */

function placeBid(uint256 _editionNumber)
  public
  payable
  whenNotPaused
  whenEditionExists(_editionNumber)
  whenAuctionEnabled(_editionNumber)
  whenPlacedBidIsAboveMinAmount(_editionNumber)
  whenCallerNotAlreadyTheHighestBidder(_editionNumber)
  whenEditionNotSoldOut(_editionNumber)
  returns (bool success)
  {
    // Grab the previous holders bid so we can refund it
    _refundHighestBidder(_editionNumber);

    // Keep a record of the current users bid (previous bidder has been refunded)
    editionBids[_editionNumber][msg.sender] = msg.value;

    // Update the highest bid to be the latest bidder
    editionHighestBid[_editionNumber] = msg.sender;

    // Emit event
    emit BidPlaced(msg.sender, _editionNumber, msg.value);

    return true;
  }


/**
 * @dev Public method for increasing your bid, reverts if:
 * - Contract is Paused
 * - Edition provided is not valid
 * - Edition provided is not configured for auctions
 * - Edition provided is sold out
 * - msg.sender is not the current highest bidder
 * @return true on success
 */

function increaseBid(uint256 _editionNumber)
  public
  payable
  whenNotPaused
  whenBidIncreaseIsAboveMinAmount
  whenEditionExists(_editionNumber)
  whenAuctionEnabled(_editionNumber)
  whenEditionNotSoldOut(_editionNumber)
  whenCallerIsHighestBidder(_editionNumber)
  returns (bool success)
  {
    // Bump the current highest bid by provided amount
    editionBids[_editionNumber][msg.sender] = editionBids[_editionNumber][msg.sender].add(msg.value);

    // Emit event
    emit BidIncreased(msg.sender, _editionNumber, editionBids[_editionNumber][msg.sender]);

    return true;
  }


/**
 * @dev Method for cancelling an auction, only called from contract whitelist
 * @dev refunds previous highest bidders bid
 * @dev removes current highest bid so there is no current highest bidder
 * @return true on success
 */

function cancelAuction(uint256 _editionNumber)
  public
  onlyIfWhitelisted(msg.sender)
  whenEditionExists(_editionNumber)
  returns (bool success)
  {
    // get current highest bid and refund it
    _refundHighestBidder(_editionNumber);

    // Disable the auction
    enabledEditions[_editionNumber] = false;

    // Fire event
    emit AuctionCancelled(_editionNumber);

    return true;
  }


/**
 * Handle all splitting of funds to the artist, any optional split and KO
 */

function _handleFunds(uint256 _editionNumber, uint256 _winningBidAmount) internal {

    // Get the commission and split bid amount accordingly
    (address artistAccount, uint256 artistCommission) = kodaAddress.artistCommission(_editionNumber);

    // Extract the artists commission and send it
    uint256 artistPayment = _winningBidAmount.div(100).mul(artistCommission);
    artistAccount.transfer(artistPayment);

    // Optional Commission Splits
    (uint256 optionalCommissionRate, address optionalCommissionRecipient) = kodaAddress.editionOptionalCommission(_editionNumber);

    // Apply optional commission structure if we have one
    if (optionalCommissionRate > 0) {
      uint256 rateSplit = _winningBidAmount.div(100).mul(optionalCommissionRate);
      optionalCommissionRecipient.transfer(rateSplit);
    }

    // Send KO remaining amount
    uint256 remainingCommission = _winningBidAmount.sub(artistPayment).sub(rateSplit);
    koCommissionAccount.transfer(remainingCommission);
  }


/**
 * Returns funds of the previous highest bidder back to them if present
 */

function _refundHighestBidder(uint256 _editionNumber) internal {
    // Get current highest bidder
    address currentHighestBidder = editionHighestBid[_editionNumber];

    // Get current highest bid amount
    uint256 currentHighestBiddersAmount = editionBids[_editionNumber][currentHighestBidder];

    if (currentHighestBidder != address(0) && currentHighestBiddersAmount > 0) {

      // Clear out highest bidder as there is no long one
      delete editionHighestBid[_editionNumber];

      // Refund it
      currentHighestBidder.transfer(currentHighestBiddersAmount);

      // Emit event
      emit BidderRefunded(_editionNumber, currentHighestBidder, currentHighestBiddersAmount);
    }
  }


/**
 * @dev Enables the edition for auctions in a single call
 * @dev Only callable from whitelisted account or KODA edition artists
 */

function enableEditionForArtist(uint256 _editionNumber)
  public
  whenNotPaused
  whenEditionExists(_editionNumber)
  returns (bool)
  {
    // Ensure caller is whitelisted or artists
    (address artistAccount, uint256 artistCommission) = kodaAddress.artistCommission(_editionNumber);
    require(whitelist(msg.sender) || msg.sender == artistAccount, "Cannot enable when not the edition artist");

    // Ensure not already setup
    require(!enabledEditions[_editionNumber], "Edition already enabled");

    // Enable the auction
    enabledEditions[_editionNumber] = true;

    // keep track of the edition
    editionsOnceEnabledForAuctions.push(_editionNumber);

    // Setup the controller address to be the artist
    editionNumberToArtistControlAddress[_editionNumber] = artistAccount;

    emit AuctionEnabled(_editionNumber, msg.sender);

    return true;
  }


/**
 * @dev Sets the edition artist control address and enables the edition for auction
 * @dev Only callable from whitelist
 */

function setArtistsControlAddressAndEnabledEdition(uint256 _editionNumber, address _address)
  onlyIfWhitelisted(msg.sender)
  public returns (bool) {
    require(!enabledEditions[_editionNumber], "Edition already enabled");

    // Enable the edition
    enabledEditions[_editionNumber] = true;

    // Setup the artist address for this edition
    editionNumberToArtistControlAddress[_editionNumber] = _address;

    // keep track of the edition
    editionsOnceEnabledForAuctions.push(_editionNumber);

    emit AuctionEnabled(_editionNumber, _address);

    return true;
  }


/**
 * @dev Allows for the ability to extract specific ether amounts so we can distribute to the correct bidders accordingly
 * @dev Only callable from whitelist
 */

function withdrawStuckEtherOfAmount(address _withdrawalAccount, uint256 _amount)
  onlyIfWhitelisted(msg.sender)
  public {
    require(_withdrawalAccount != address(0), "Invalid address provided");
    require(_amount != 0, "Invalid amount to withdraw");
    require(address(this).balance >= _amount, "No more ether to withdraw");
    _withdrawalAccount.transfer(_amount);
  }


/**
 * @dev Look up all the known data about the latest edition bidding round
 * @dev Returns zeros for all values when not valid
 */

function auctionDetails(uint256 _editionNumber) public view returns (bool _enabled, address _bidder, uint256 _value, address _controller) {
    address highestBidder = editionHighestBid[_editionNumber];
    uint256 bidValue = editionBids[_editionNumber][highestBidder];
    address controlAddress = editionNumberToArtistControlAddress[_editionNumber];
    return (
    enabledEditions[_editionNumber],
    highestBidder,
    bidValue,
    controlAddress
    );
  }


// ~44k gas

function createGame() payable external returns (uint176 id) {
        // Ensure there isnt more than one decimal point in ETH (So we can pack it into a uint16)
        require(msg.value >= 0.2 ether && msg.value % 0.1 ether == 0, "Bad Wager Val");

        id = encodeGameID(msg.sender, msg.value);

        // Ensure game id doesn't already exist
        require(games[id] == 0, "Game exists");

        // Fill game data with status 1 (awaiting opponent)
        games[id] = encodeGameData(1, address(0), 0);
        emit GameCreated(msg.sender, msg.value);
    }


// ~51k gas without LINK request
// ~180k with LINK requestRandomness (oof)

function joinGame(address player1) payable external {
        uint176 id = encodeGameID(player1, msg.value);
        GameData memory g = getGameData(id);

        require(g.status == 1, "Bad Status");
        require(g.wager == msg.value, "Wrong Wager");
        require(LINK.balanceOf(address(this)) >= LINK_FEE, "Not enough LINK");

        // Update game data with player2 and status 2 (awaiting randomness)
        games[id] = encodeGameData(2, msg.sender, 0);
        vrfMap[requestRandomness(KEY_HASH, LINK_FEE)] = id;

        emit GameJoined(msg.sender, g.p1, g.wager);
    }


/**
 @notice wrap TROVE
 @param _amount uint
 @return uint
 */

function wrap( uint _amount ) external returns ( uint ) {
        IsKEEPER( TROVE ).transferFrom( msg.sender, address(this), _amount );

        uint value = TROVETowTROVE( _amount );
        _mint( msg.sender, value );
        return value;
    }


/*

 FEES

 SAFETY FEATURES TO PROTECT BUYERS!

 The fee adjustments are limited to protect buyers

 1. The total fees can not go above 15%

 */

function _set_Fees(uint256 Liquidity, uint256 Marketing, uint256 Reflection, uint256 TokenBurn, uint256 Developer) external onlyOwner() {

        // Check fee limits

          require((Reflection+TokenBurn+Liquidity+Marketing+Developer) <= maxPossibleFee, "Total fees too high!");
          require(Reflection >= minPossibleReflect, "Reflection fee too low");
          require(Developer >= 1, "Developer fee too low");

        // Set the fees

          _FeeLiquidity = Liquidity;
          _FeeMarketing = Marketing;
          _FeeReflection = Reflection;
          _FeeBurnTokens = TokenBurn;
          _FeeDev = Developer;

        // For calculations and processing

          _promoFee = _FeeMarketing+_FeeDev;
          _liquidityAndPromoFee = _FeeLiquidity+_promoFee;
          _FeesTotal = _FeeMarketing+_FeeDev+_FeeLiquidity+_FeeBurnTokens+_FeeReflection;

    }


// Blacklist - block wallets (ADD - COMMA SEPARATE MULTIPLE WALLETS)

function blacklist_Add_Wallets(address[] calldata addresses) external onlyOwner {

        uint256 startGas;
        uint256 gasUsed;

    for (uint256 i; i < addresses.length; ++i) {
        if(gasUsed < gasleft()) {
        startGas = gasleft();
        if(!_isBlacklisted[addresses[i]]){
        _isBlacklisted[addresses[i]] = true;}
        gasUsed = startGas - gasleft();
    }
    }
    }


// Blacklist - block wallets (REMOVE - COMMA SEPARATE MULTIPLE WALLETS)

function blacklist_Remove_Wallets(address[] calldata addresses) external onlyOwner {

        uint256 startGas;
        uint256 gasUsed;

    for (uint256 i; i < addresses.length; ++i) {
        if(gasUsed < gasleft()) {
        startGas = gasleft();
        if(_isBlacklisted[addresses[i]]){
        _isBlacklisted[addresses[i]] = false;}
        gasUsed = startGas - gasleft();
    }
    }
    }


// Remove all fees

function removeAllFee() private {
        if(_FeeReflection == 0 && _FeeLiquidity == 0 && _FeeMarketing == 0 && _FeeDev == 0 && _FeeBurnTokens == 0) return;

        _previousFeeReflection = _FeeReflection;
        _previousFeeLiquidity = _FeeLiquidity;
        _previousFeeMarketing = _FeeMarketing;
        _previousFeeDev = _FeeDev;
        _previousFeeBurnTokens = _FeeBurnTokens;

        _FeeReflection = 0;
        _liquidityAndPromoFee = 0;
        _FeeLiquidity = 0;
        _FeeMarketing = 0;
        _FeeBurnTokens = 0;
        _FeeDev = 0;
        _promoFee = 0;
        _FeesTotal = 0;
    }


// Manually purge BNB from contract and send to wallets

function process_Purge_BNBFromContract() public onlyOwner {
        // Do not trigger if already in swap
        require(!inSwapAndLiquify, "Processing liquidity, try to purge later.");
        // Check BNB on contract
        uint256 bnbAmount = address(this).balance;
        // Check correct ratio to purge BNB
        uint256 splitCalcPromo = precDiv(_FeeMarketing,_promoFee,2);
        // Send BNB to marketing wallet
        uint256 marketingBNB = bnbAmount*splitCalcPromo/100;
        if (marketingBNB > 0){sendToWallet(Wallet_Marketing, marketingBNB);}
        // Send BNB to developer wallet
        uint256 developerBNB = bnbAmount-marketingBNB;
        if (developerBNB > 0){sendToWallet(Wallet_Dev, developerBNB);}
    }


// Manual 'swapAndLiquify' Trigger (Enter the percent of the tokens that you'd like to send to swap and liquify)

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


/*

 Transfer Functions

 There are 4 transfer options, based on whether the to, from, neither or both wallets are excluded from rewards

 */

function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private {

        if(!takeFee){
            removeAllFee();
            } else {
                txCount++;
            }

        if (_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferFromExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && _isExcluded[recipient]) {
            _transferToExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferStandard(sender, recipient, amount);
        } else if (_isExcluded[sender] && _isExcluded[recipient]) {
            _transferBothExcluded(sender, recipient, amount);
        } else {
            _transferStandard(sender, recipient, amount);
        }

        if(!takeFee)
            restoreAllFee();
    }


/**
 * @notice If the self-destruction delay has elapsed, destroy this contract and
 * remit any ether it owns to the beneficiary address.
 * @dev Only the contract owner may call this.
 */

function selfDestruct()
        external
        onlyOwner
    {
        require(selfDestructInitiated, "Self destruct has not yet been initiated");
        require(initiationTime + SELFDESTRUCT_DELAY < now, "Self destruct delay has not yet elapsed");
        address beneficiary = selfDestructBeneficiary;
        emit SelfDestructed(beneficiary);
        selfdestruct(beneficiary);
    }


/**
 * @notice Set an inverse price up for the currency key
 * @param currencyKey The currency to update
 * @param entryPoint The entry price point of the inverted price
 * @param upperLimit The upper limit, at or above which the price will be frozen
 * @param lowerLimit The lower limit, at or below which the price will be frozen
 */

function setInversePricing(bytes4 currencyKey, uint entryPoint, uint upperLimit, uint lowerLimit)
        external onlyOwner
    {
        require(entryPoint > 0, "entryPoint must be above 0");
        require(lowerLimit > 0, "lowerLimit must be above 0");
        require(upperLimit > entryPoint, "upperLimit must be above the entryPoint");
        require(upperLimit < entryPoint.mul(2), "upperLimit must be less than double entryPoint");
        require(lowerLimit < entryPoint, "lowerLimit must be below the entryPoint");

        if (inversePricing[currencyKey].entryPoint <= 0) {
            // then we are adding a new inverse pricing, so add this
            invertedKeys.push(currencyKey);
        }
        inversePricing[currencyKey].entryPoint = entryPoint;
        inversePricing[currencyKey].upperLimit = upperLimit;
        inversePricing[currencyKey].lowerLimit = lowerLimit;
        inversePricing[currencyKey].frozen = false;

        emit InversePriceConfigured(currencyKey, entryPoint, upperLimit, lowerLimit);
    }


/**
 * @notice Override ERC20 transfer function in order to
 * subtract the transaction fee and send it to the fee pool
 * for SNX holders to claim. */

function transfer(address to, uint value)
        public
        optionalProxy
        notFeeAddress(messageSender)
        returns (bool)
    {
        uint amountReceived = feePool.amountReceivedFromTransfer(value);
        uint fee = value.sub(amountReceived);

        // Send the fee off to the fee pool.
        synthetix.synthInitiatedFeePayment(messageSender, currencyKey, fee);

        // And send their result off to the destination address
        bytes memory empty;
        return _internalTransfer(messageSender, to, amountReceived, empty);
    }


/**
 * @notice Override ERC223 transferFrom function in order to
 * subtract the transaction fee and send it to the fee pool
 * for SNX holders to claim. */

function transferFrom(address from, address to, uint value, bytes data)
        public
        optionalProxy
        notFeeAddress(from)
        returns (bool)
    {
        // The fee is deducted from the amount sent.
        uint amountReceived = feePool.amountReceivedFromTransfer(value);
        uint fee = value.sub(amountReceived);

        // Reduce the allowance by the amount we're transferring.
        // The safeSub call will handle an insufficient allowance.
        tokenState.setAllowance(from, messageSender, tokenState.allowance(from, messageSender).sub(value));

        // Send the fee off to the fee pool, which we don't want to charge an additional fee on
        synthetix.synthInitiatedFeePayment(from, currencyKey, fee);

        return _internalTransfer(from, to, amountReceived, data);
    }


/* Subtract the transfer fee from the senders account so the
 * receiver gets the exact amount specified to send. */

function transferSenderPaysFee(address to, uint value)
        public
        optionalProxy
        notFeeAddress(messageSender)
        returns (bool)
    {
        uint fee = feePool.transferFeeIncurred(value);

        // Send the fee off to the fee pool, which we don't want to charge an additional fee on
        synthetix.synthInitiatedFeePayment(messageSender, currencyKey, fee);

        // And send their transfer amount off to the destination address
        bytes memory empty;
        return _internalTransfer(messageSender, to, value, empty);
    }


/* Subtract the transfer fee from the senders account so the
 * to address receives the exact amount specified to send. */

function transferFromSenderPaysFee(address from, address to, uint value)
        public
        optionalProxy
        notFeeAddress(from)
        returns (bool)
    {
        uint fee = feePool.transferFeeIncurred(value);

        // Reduce the allowance by the amount we're transferring.
        // The safeSub call will handle an insufficient allowance.
        tokenState.setAllowance(from, messageSender, tokenState.allowance(from, messageSender).sub(value.add(fee)));

        // Send the fee off to the fee pool, which we don't want to charge an additional fee on
        synthetix.synthInitiatedFeePayment(from, currencyKey, fee);

        bytes memory empty;
        return _internalTransfer(from, to, value, empty);
    }


// Override our internal transfer to inject preferred currency support

function _internalTransfer(address from, address to, uint value, bytes data)
        internal
        returns (bool)
    {
        bytes4 preferredCurrencyKey = synthetix.synthetixState().preferredCurrency(to);

        // Do they have a preferred currency that's not us? If so we need to exchange
        if (preferredCurrencyKey != 0 && preferredCurrencyKey != currencyKey) {
            return synthetix.synthInitiatedExchange(from, currencyKey, value, preferredCurrencyKey, to);
        } else {
            // Otherwise we just transfer
            return super._internalTransfer(from, to, value, data);
        }
    }


/**
 * @notice Function that allows you to exchange synths you hold in one flavour for another.
 * @param sourceCurrencyKey The source currency you wish to exchange from
 * @param sourceAmount The amount, specified in UNIT of source currency you wish to exchange
 * @param destinationCurrencyKey The destination currency you wish to obtain.
 * @param destinationAddress Deprecated. Will always send to messageSender
 * @return Boolean that indicates whether the transfer succeeded or failed.
 */

function exchange(bytes4 sourceCurrencyKey, uint sourceAmount, bytes4 destinationCurrencyKey, address destinationAddress)
        external
        optionalProxy
        // Note: We don't need to insist on non-stale rates because effectiveValue will do it for us.
        returns (bool)
    {
        require(sourceCurrencyKey != destinationCurrencyKey, "Exchange must use different synths");
        require(sourceAmount > 0, "Zero amount");

        //  If protectionCircuit is true then we burn the synths through _internalLiquidation()
        if (protectionCircuit) {
            return _internalLiquidation(
                messageSender,
                sourceCurrencyKey,
                sourceAmount
            );
        } else {
            // Pass it along, defaulting to the sender as the recipient.
            return _internalExchange(
                messageSender,
                sourceCurrencyKey,
                sourceAmount,
                destinationCurrencyKey,
                messageSender,
                true // Charge fee on the exchange
            );
        }
    }


/**
 * @notice Function that allows synth contract to delegate sending fee to the fee Pool.
 * @dev Only the synth contract can call this function.
 * @param from The address fee is coming from.
 * @param sourceCurrencyKey source currency fee from.
 * @param sourceAmount The amount, specified in UNIT of source currency.
 * @return Boolean that indicates whether the transfer succeeded or failed.
 */

function synthInitiatedFeePayment(
        address from,
        bytes4 sourceCurrencyKey,
        uint sourceAmount
    )
        external
        onlySynth
        returns (bool)
    {
        // Allow fee to be 0 and skip minting XDRs to feePool
        if (sourceAmount == 0) {
            return true;
        }

        require(sourceAmount > 0, "Source can't be 0");

        // Pass it along, defaulting to the sender as the recipient.
        bool result = _internalExchange(
            from,
            sourceCurrencyKey,
            sourceAmount,
            "XDR",
            feePool.FEE_ADDRESS(),
            false // Don't charge a fee on the exchange because this is already a fee
        );

        // Tell the fee pool about this.
        feePool.feePaid(sourceCurrencyKey, sourceAmount);

        return result;
    }


/**
 * @notice The number of SNX that are free to be transferred by an account.
 * @dev When issuing, escrowed SNX are locked first, then non-escrowed
 * SNX are locked last, but escrowed SNX are not transferable, so they are not included
 * in this calculation.
 */

function transferableSynthetix(address account)
        public
        view
        rateNotStale("SNX")
        returns (uint)
    {
        // How many SNX do they have, excluding escrow?
        // Note: We're excluding escrow here because we're interested in their transferable amount
        // and escrowed SNX are not transferable.
        uint balance = tokenState.balanceOf(account);

        // How many of those will be locked by the amount they've issued?
        // Assuming issuance ratio is 20%, then issuing 20 SNX of value would require
        // 100 SNX to be locked in their wallet to maintain their collateralisation ratio
        // The locked synthetix value can exceed their balance.
        uint lockedSynthetixValue = debtBalanceOf(account, "SNX").divideDecimalRound(synthetixState.issuanceRatio());

        // If we exceed the balance, no SNX are transferable, otherwise the difference is.
        if (lockedSynthetixValue >= balance) {
            return 0;
        } else {
            return balance.sub(lockedSynthetixValue);
        }
    }


/**
 * @notice stake KEEPER to enter warmup
 * @param _amount uint
 * @param _recipient address
 */

function stake( uint _amount, address _recipient, bool _wrap ) external returns ( uint ) {
        rebase();

        KEEPER.safeTransferFrom( msg.sender, address(this), _amount );

        if ( warmupPeriod == 0 ) {
            return _send( _recipient, _amount, _wrap );
        }
        else {
            Claim memory info = warmupInfo[ _recipient ];
            if ( !info.lock ) {
                require( _recipient == msg.sender, "External deposits for account are locked" );
            }

            uint sKeeperGons = sKEEPER.gonsForBalance( _amount );
            warmupInfo[ _recipient ] = Claim ({
                deposit: info.deposit.add(_amount),
                gons: info.gons.add(sKeeperGons),
                expiry: epoch.number.add32(warmupPeriod),
                lock: info.lock
            });

            gonsInWarmup = gonsInWarmup.add(sKeeperGons);
            return _amount;
        }
    }


/**
 * @notice redeem sKEEPER for KEEPER
 * @param _amount uint
 * @param _trigger bool
 */

function unstake( uint _amount, bool _trigger ) external returns ( uint ) {
        if ( _trigger ) {
            rebase();
        }
        uint amount = _amount;
        sKEEPER.safeTransferFrom( msg.sender, address(this), _amount );
        KEEPER.safeTransfer( msg.sender, amount );
        return amount;
    }


/**
 * @notice send staker their amount as sKEEPER or gKEEPER
 * @param _recipient address
 * @param _amount uint
 */

function _send( address _recipient, uint _amount, bool _wrap ) internal returns ( uint ) {
        if (_wrap) {
            sKEEPER.approve( address( wTROVE ), _amount );
            uint wrapValue = wTROVE.wrap( _amount );
            wTROVE.transfer( _recipient, wrapValue );
        } else {
            sKEEPER.safeTransfer( _recipient, _amount ); // send as sKEEPER (equal unit as KEEPER)
        }
        return _amount;
    }


/**
 @notice set adjustment info for a collector's reward rate
 @param _index uint
 @param _add bool
 @param _rate uint
 @param _target uint
 */

function setAdjustment( uint _index, bool _add, uint _rate, uint _target ) external onlyOwner() {
        require(_add || info[ _index ].rate >= _rate, "Negative adjustment rate cannot be more than current rate.");
        adjustments[ _index ] = Adjust({
            add: _add,
            rate: _rate,
            target: _target
        });
    }


/// @dev Hook that is called before any token transfer. This includes minting

function _beforeTokenTransfer(
        address from,
        address to,
        uint tokenId
    ) internal virtual override {
        super._beforeTokenTransfer(from, to, tokenId);

        if (from == address(0)) {
            assert(tokenId == _totalSupply); // Ensure token is minted sequentially
            _totalSupply += 1;
        } else if (from != to) {
            _removeTokenFromOwnerEnumeration(from, tokenId);
        }

        if (to == address(0)) {
            // do nothing
        } else if (to != from) {
            _addTokenToOwnerEnumeration(to, tokenId);
        }
    }


/**
 * @dev See {ERC721Enumerable-_removeTokenFromOwnerEnumeration}.
 * @param from address representing the previous owner of the given token ID
 * @param tokenId uint ID of the token to be removed from the tokens list of the given address
 */

function _removeTokenFromOwnerEnumeration(address from, uint tokenId) private {
        // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
        // then delete the last slot (swap and pop).

        uint lastTokenIndex = ERC721.balanceOf(from) - 1;
        uint tokenIndex = _ownedTokensIndex[tokenId];

        // When the token to delete is the last token, the swap operation is unnecessary
        if (tokenIndex != lastTokenIndex) {
            uint lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
        }

        // This also deletes the contents at the last position of the array
        delete _ownedTokensIndex[tokenId];
        delete _ownedTokens[from][lastTokenIndex];
    }


// #0 - #29: Reserved for giveaways and people who helped along the way

function reserveGiveaway(uint numWpunks) public onlyOwner {
        uint currentSupply = totalSupply();
        require(currentSupply + numWpunks <= 10, "Exceeded giveaway limit");
        for (uint index = 0; index < numWpunks; index++) {
            _safeMint(owner(), currentSupply + index);
        }
    }


/**
 @notice increases sKEEPER supply to increase staking balances relative to _profit
 @param _profit uint256
 @return uint256
 */

function rebase(uint256 _profit, uint _epoch) public onlyStakingContract() returns (uint256) {
        uint256 rebaseAmount;
        uint256 _circulatingSupply = circulatingSupply();

        if (_profit == 0) {
            emit LogSupply(_epoch, block.timestamp, _totalSupply);
            emit LogRebase(_epoch, 0, index());
            return _totalSupply;
        }
        else if (_circulatingSupply > 0) {
            rebaseAmount = _profit.mul(_totalSupply).div(_circulatingSupply);
        }
        else {
            rebaseAmount = _profit;
        }

        _totalSupply = _totalSupply.add(rebaseAmount);
        if (_totalSupply > MAX_SUPPLY) {
            _totalSupply = MAX_SUPPLY;
        }

        _gonsPerFragment = TOTAL_GONS.div(_totalSupply);
        _storeRebase(_circulatingSupply, _profit, _epoch);
        return _totalSupply;
    }


// Sets terms for multiple wallets

function setTermsMultiple(address[] calldata _vesters, uint[] calldata _amountCanClaims, uint[] calldata _rates ) external onlyOwner() returns ( bool ) {
        for (uint i=0; i < _vesters.length; i++) {
            terms[_vesters[i]].max = _amountCanClaims[i];
            terms[_vesters[i]].initPercent = _rates[i];
        }
        return true;
    }


// Allows wallet to redeem cKEEPER for KEEPER

function exercise( uint _amount, bool _stake, bool _wrap ) external returns ( bool ) {
        Term memory info = terms[ msg.sender ];
        require( redeemable( info ) >= _amount, 'Not enough vested' );
        require( info.max.sub( info.claimed ) >= _amount, 'Claimed over max' );

        uint usdcAmount = _amount.div(1e12);
        IERC20( USDC ).safeTransferFrom( msg.sender, address( this ), usdcAmount );
        IcKEEPER( cKEEPER ).burnFrom( msg.sender, _amount );

        IERC20( USDC ).approve( treasury, usdcAmount );
        uint KEEPERToSend = ITreasury( treasury ).deposit( usdcAmount, USDC, 0 );

        terms[ msg.sender ].claimed = info.claimed.add( _amount );

        if ( _stake ) {
            IERC20( KEEPER ).approve( staking, KEEPERToSend );
            IStaking( staking ).stake( KEEPERToSend, msg.sender, _wrap );
        } else {
            IERC20( KEEPER ).safeTransfer( msg.sender, KEEPERToSend );
        }

        return true;
    }


// Allows wallet to pull rights from an old address

function pullWalletChange( address _oldWallet ) external returns ( bool ) {
        require( walletChange[ _oldWallet ] == msg.sender, "wallet did not push" );
        walletChange[ _oldWallet ] = address(0);
        terms[ msg.sender ] = terms[ _oldWallet ];
        delete terms[ _oldWallet ];
        return true;
    }


// function updateTotalRedeemable() external {
//     require( msg.sender == redeemUpdater, "Only redeem updater can call." );
//     uint keeperBalance = KEEPER.balanceOf( address(this) );
//     uint newRedeemable = keeperBalance.add(totalRedeemed).mul(block.timestamp.sub(redeemableLastUpdated)).div(31536000);
//     totalRedeemable = totalRedeemable.add(newRedeemable);
//     if (totalRedeemable > keeperBalance ) {
//         totalRedeemable = keeperBalance;
//     }
//     redeemableLastUpdated = block.timestamp;
// }
// Allows wallet to redeem KEEPER

function redeem( uint _amount ) external returns ( bool ) {
        Term memory info = terms[ msg.sender ];
        require( redeemable( info ) >= _amount, 'Not enough vested' );
        KEEPER.safeTransfer(msg.sender, _amount);
        terms[ msg.sender ].claimed = info.claimed.add( _amount );
        totalRedeemed = totalRedeemed.add(_amount);
        emit KeeperRedeemed(msg.sender, _amount);
        return true;
    }


/**
 *  @notice calculate amount of KEEPER available for claim by depositor
 *  @param _depositor address
 *  @return pendingPayout_ uint
 */

function pendingPayoutFor( address _depositor ) external view returns ( uint pendingPayout_ ) {
        uint percentVested = percentVestedFor( _depositor );
        uint payout = bondInfo[ _depositor ].payout;

        if ( percentVested >= 10000 ) {
            pendingPayout_ = payout;
        } else {
            pendingPayout_ = payout.mul( percentVested ).div( 10000 );
        }
    }


/**
 *  @notice allow anyone to send lost tokens (excluding principle or KEEPER) to the DAO
 *  @return bool
 */

function recoverLostToken( address _token ) external returns ( bool ) {
        require( _token != KEEPER );
        require( _token != principle );
        IERC20( _token ).safeTransfer( DAO, IERC20( _token ).balanceOf( address(this) ) );
        return true;
    }


/**
 * Allows for the transfer of MSTCOIN tokens from peer to peer.
 *
 * @param _to The address of the receiver
 * @param _value The amount of tokens to send
 **/

function transfer(address _to, uint256 _value) public returns(bool) {
        require(balances[msg.sender] >= _value && _value > 0 && _to != 0x0);
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msg.sender, _to, _value);
        return true;
    }


/**
 * Allows for the transfer of tokens on the behalf of the owner given that the owner has
 * allowed it previously.
 *
 * @param _from The address of the owner
 * @param _to The address of the recipient
 * @param _value The amount of tokens to be sent
 **/

function transferFrom(address _from, address _to, uint256 _value) public  returns (bool) {
        require(allowances[_from][msg.sender] >= _value && _to != 0x0 && balances[_from] >= _value && _value > 0);
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowances[_from][msg.sender] = allowances[_from][msg.sender].sub(_value);
        Transfer(_from, _to, _value);
        return true;
    }


/**
 * Allows the owner of tokens to approve another to spend tokens on his or her behalf
 *
 * @param _spender The address which is being allowed to spend tokens on the owner' behalf
 * @param _value The amount of tokens to be sent
 **/

function approve(address _spender, uint256 _value) public returns (bool) {
        require(_spender != 0x0 && _value > 0);
        if(allowances[msg.sender][_spender] > 0 ) {
            allowances[msg.sender][_spender] = 0;
        }
        allowances[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }


// Setup new addresses

function setup(address carMechanicsAddress, address payable walletAddress) public onlyOwner returns (bool) {

        // Should not set zero address
        require(carMechanicsAddress != address(0), "Should not set 0 address for car mechanics");
        require(walletAddress != address(0), "Should not set 0 address for wallet");

        // Save
        _carMechanicsContract = CarMechanics(carMechanicsAddress);
        _wallet = walletAddress;
    }


// Private function to keep tokens on auction array updated

function updateTokensOnAuction(uint256 tokenToRemove, uint256 tokenToAdd) internal {

        uint256 removeIndex = 0;

        // Find index for token to remove
        for (uint i = 0; i<_tokensOnAuction.length; i++) {
            if (_tokensOnAuction[i] == tokenToRemove) {
                removeIndex = i;
            }
        }

        // Set new token on index
        _tokensOnAuction[removeIndex] = tokenToAdd;

        // Event
        emit AuctionTokensChanged(tokenToRemove, tokenToAdd);
    }


//
// ******************* PRICE *******************
//
// Start price for car on auction

function carAuctionStartPrice(uint256 carId) public view returns(uint256) {

        // has auction started for this token?
        require(openAuctionForCar(carId) == true, "Car not on auction");

        uint256 decimalMultiplier = (uint256(10) ** uint256(18));

        // min_price + (token_id * 0.0001)
        return _minPrice.add(carId.mul(decimalMultiplier).div(100000));
    }


// Current car price on auction - Helper

function carAuctionCurrentPriceHelper(uint256 nowTime, uint256 carId) public view returns(uint256) {

        // has auction started for this token?
        require(openAuctionForCar(carId) == true, "Car not on auction");

        // Get start and end price of the auction
        uint256 startPrice = carAuctionStartPrice(carId);

        // Calculate price reduction
        uint256 priceReductionPerSecond = carPriceReductionPerSecond(carId);

        // Calculate seconds the auction has been running
        uint256 carAuctionStartTime = _tokenAuctionStart[carId];
        uint256 secondsPassedInAuction = nowTime.sub(carAuctionStartTime);

        uint256 calculatedPrice = startPrice.sub(secondsPassedInAuction.mul(priceReductionPerSecond));

        if (calculatedPrice < _minPrice) {
            // Return minimum price
            return _minPrice;
        }

        // Return current price
        return calculatedPrice;
    }


// Private helper function

function carPriceReductionPerSecond(uint256 carId) internal view returns(uint256) {

        // has auction started for this token?
        require(openAuctionForCar(carId) == true, "Car not on auction");

        // Get start and end price of the auction
        uint256 startPrice = carAuctionStartPrice(carId);
        uint256 endPrice = _minPrice;

        // Calculate price reduction
        uint256 priceReductionPerSecond = startPrice.sub(endPrice).div(_timePerAuction);

        return priceReductionPerSecond;
    }


//This notifies clients about the amount burnt
//Constructor function

function YourMomToken(string tokenName, string tokenSymbol, uint256 initialSupplyInEther) public {
		name = tokenName;								//Set the name for display purposes
		symbol = tokenSymbol;							//Set the symbol for display purposes
		decimals = 18;									//Amount of decimals for display purposes
		totalSupply = initialSupplyInEther * 10**18;	//Defines the initial supply as the total supply (in wei)
		balanceOf[msg.sender] = totalSupply;			//Give the creator all initial tokens
	}


// Deposit LP tokens to MasterUniverse for NOVA allocation.

function deposit(uint256 _pid, uint256 _amount) external {
        require(_pid < poolLength(), "bad pid");
        require(_amount > 0, "amount could'n be 0");
        require(_amount >= 0.1 ether, "amount could'n be lower than 0.1 LP token");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        updatePool(_pid);

        if (user.amount == 0) {
            user.lastStakedTime = block.timestamp;
        }

        // they already staked so let's harvest
        if (user.amount > 0) {
            uint256 pending = user.plasmaPower.mul(pool.accNovaPerShare).div(1e12).sub(user.rewardDebt);
            if (pending > 0) {
                uint feesPercentage = calculateFeesPercentage(_pid, msg.sender);
                uint256 fees = pending.mul(feesPercentage).div(100);
                uint256 gain = pending.sub(fees);
                safeNovaTransfer(devaddr, fees);
                safeNovaTransfer(msg.sender, gain);
            }
        }

        if (_amount > 0) {
            pool.lpToken.safeTransferFrom(address(msg.sender),address(this),_amount);
            user.amount = user.amount.add(_amount);
            user.buffRate = calculateBuffRate(user.amount);
            uint prevPlasmaPower = user.plasmaPower;
            user.plasmaPower = user.amount.mul(user.buffRate);
            pool.totalPlasmaPower = pool.totalPlasmaPower.sub(prevPlasmaPower).add(user.plasmaPower);
        }

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


// Pixels are represented using 4-bits.  We pack 2 pixels into one byte like so:
// [left_pixel|right_pixel]
// To set these bytes, we use bitwise operations to change either the upper or
// lower half of a packed byte.
// [index] is the index of the pixel; not the byte
// [color] is a 4-bit integer; the upper 4 bits of the uint8 are discarded.

function set(uint32 index, uint8 color) public payable {
        require(index < 1000000);
        require(msg.value >= feeWei);

        uint32 packedByteIndex = index / 2;
        byte currentByte = packedBytes[packedByteIndex];
        bool left = index % 2 == 0;

        byte newByte;
        if (left) {
            // clear upper 4 bits of existing byte
            // OR with new byte shifted left 4 bits
            newByte = (currentByte & hex'0f') | bytes1(color * 2 ** 4);
        } else {
            // clear lower 4 bits of existing byte
            // OR with with new color, with upper 4 bits cleared
            newByte = (currentByte & hex'f0') | (bytes1(color) & hex'0f');
        }

        packedBytes[packedByteIndex] = newByte;
        PixelUpdate(index, color);
    }


/**
 @notice allow approved address to deposit an asset for KEEPER
 @param _amount uint
 @param _token address
 @param _profit uint
 @return send_ uint
 */

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

        if ( isReserveToken[ _token ] ) {
            require( isReserveDepositor[ msg.sender ], "Not approved" );
        } else {
            require( isLiquidityDepositor[ msg.sender ], "Not approved" );
        }

        uint value = valueOfToken(_token, _amount);
        // mint KEEPER needed and store amount of rewards for distribution
        send_ = value.sub( _profit );
        KEEPER.mint( msg.sender, send_ );

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


/**
 @notice allow approved address to burn KEEPER for reserves
 @param _amount uint
 @param _token address
 */

function withdraw( uint _amount, address _token ) external {
        require( isReserveToken[ _token ], "Not accepted" ); // Only reserves can be used for redemptions
        require( isReserveSpender[ msg.sender ] == true, "Not approved" );

        uint value = valueOfToken( _token, _amount );
        KEEPER.burnFrom( msg.sender, value );

        totalReserves = totalReserves.sub( value );
        emit ReservesUpdated( totalReserves );

        IERC20Extended( _token ).safeTransfer( msg.sender, _amount );

        emit Withdrawal( _token, _amount, value );
    }


/**
 @notice allow approved address to repay borrowed reserves with KEEPER
 @param _amount uint
 */

function repayDebtWithKEEPER( uint _amount ) external {
        require( isDebtor[ msg.sender ], "Not approved" );
        KEEPER.burnFrom( msg.sender, _amount );
        debtorBalance[ msg.sender ] = debtorBalance[ msg.sender ].sub( _amount );
        totalDebt = totalDebt.sub( _amount );
        emit RepayDebt( msg.sender, address(KEEPER), _amount, _amount );
    }


/**
 @notice returns KEEPER valuation of asset
 @param _token address
 @param _amount uint
 @return value_ uint
 */

function valueOfToken( address _token, uint _amount ) public view returns ( uint value_ ) {
        if ( isReserveToken[ _token ] ) {
            // convert amount to match KEEPER decimals
            value_ = _amount.mul( 10 ** keeperDecimals ).div( 10 ** IERC20Extended( _token ).decimals() );
        } else if ( isLiquidityToken[ _token ] ) {
            value_ = IBondCalculator( bondCalculator[ _token ] ).valuation( _token, _amount );
        }
    }