contract GlobalsAndUtility is ERC20 {
    /*  XfLobbyEnter
    */
    event XfLobbyEnter(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed entryIndex,
        uint256 indexed rawAmount
    );

    /*  XfLobbyExit 
    */
    event XfLobbyExit(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed entryIndex,
        uint256 indexed xfAmount,
        address indexed referrerAddr
    );

    /*  DailyDataUpdate
    */
    event DailyDataUpdate(
        address indexed updaterAddr,
        uint256 timestamp,
        uint256 beginDay,
        uint256 endDay
    );

    /*  StakeStart
    */
    event StakeStart(
        uint40 indexed stakeId,
        address indexed stakerAddr,
        uint256 stakedGuns,
        uint256 stakeShares,
        uint256 stakedDays
    );
    
    /*  StakeGoodAccounting
    */
    event StakeGoodAccounting(
        uint40 indexed stakeId,
        address indexed stakerAddr,
        address indexed senderAddr,
        uint256 stakedGuns,
        uint256 stakeShares,
        uint256 payout,
        uint256 penalty
    );

    /*  StakeEnd 
    */
    event StakeEnd(
        uint40 indexed stakeId,
        uint40 prevUnlocked,
        address indexed stakerAddr,
        uint256 lockedDay,
        uint256 servedDays,
        uint256 stakedGuns,
        uint256 stakeShares,
        uint256 dividends,
        uint256 payout,
        uint256 penalty,
        uint256 stakeReturn
    );

    /*  ShareRateChange 
    */
    event ShareRateChange(
        uint40 indexed stakeId,
        uint256 timestamp,
        uint256 newShareRate
    );

    /* 	CSN allocation share address
	*	Used for 5% drip into CSN auction
	*	95% of the first 2 days public auction will be redistributed over the next 30 days to balance auction lobby healthy.
	*/
    address payable internal constant FLUSH_ADDR = 0xcE3D57d817dC3B83899907BFec99C7EF2E2AF35d;

    uint8 internal LAST_FLUSHED_DAY = 1;

    /* ERC20 constants */
    string public constant name = "Community Staking Network in ETH";
    string public constant symbol = "CSNE";
    uint8 public constant decimals = 8;

    /* Suns per Satoshi = 10,000 * 1e8 / 1e8 = 1e4 */
    uint256 private constant SUNS_PER_DIV = 10 ** uint256(decimals); // 1e8

    /* Time of contract launch (Dec 7th, 00:00:00 UTC, Auction Day 1 Starts Dec 8th, 00:00:00 UTC) */
    uint256 internal constant LAUNCH_TIME = 1607299200;

    /* Start of claim phase */
    uint256 internal constant PRE_CLAIM_DAYS = 1;
    /* reduce amount of tokens to 2500000 */
    uint256 internal constant CLAIM_STARTING_AMOUNT = 2500000 * (10 ** 8);
    /* reduce amount of tokens to 1000000 */
    uint256 internal constant CLAIM_LOWEST_AMOUNT = 1000000 * (10 ** 8);
    uint256 internal constant CLAIM_PHASE_START_DAY = PRE_CLAIM_DAYS;

    /* Number of words to hold 1 bit for each transform lobby day */
    uint256 internal constant XF_LOBBY_DAY_WORDS = ((1 + (50 * 7)) + 255) >> 8;

    /* Stake timing parameters */
    uint256 internal constant MIN_STAKE_DAYS = 1;

    uint256 internal constant MAX_STAKE_DAYS = 30; //1 month

    uint256 internal constant EARLY_PENALTY_MIN_DAYS = 90;

    uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
    uint256 internal constant LATE_PENALTY_GRACE_DAYS = LATE_PENALTY_GRACE_WEEKS * 7;

    uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
    uint256 internal constant LATE_PENALTY_SCALE_DAYS = LATE_PENALTY_SCALE_WEEKS * 7;

    /* Stake shares Longer Pays Better bonus constants used by _stakeStartBonusGuns() */
    uint256 private constant LPB_BONUS_PERCENT = 20;
    uint256 private constant LPB_BONUS_MAX_PERCENT = 200;
    uint256 internal constant LPB = 364 * 100 / LPB_BONUS_PERCENT;
    uint256 internal constant LPB_MAX_DAYS = LPB * LPB_BONUS_MAX_PERCENT / 100;

    uint256 private constant BPB_BONUS_PERCENT = 10;
    uint256 private constant BPB_MAX_DIV = 7 * 1e6;
    uint256 internal constant BPB_MAX_SUNS = BPB_MAX_DIV * SUNS_PER_DIV;
    uint256 internal constant BPB = BPB_MAX_SUNS * 100 / BPB_BONUS_PERCENT;

    /* Share rate is scaled to increase precision */
    uint256 internal constant SHARE_RATE_SCALE = 1e5;	
    /* Share rate max (after scaling) */	
    uint256 internal constant SHARE_RATE_UINT_SIZE = 40;	
    uint256 internal constant SHARE_RATE_MAX = (1 << SHARE_RATE_UINT_SIZE) - 1;	
    /* weekly staking bonus */	
    uint8 internal constant BONUS_DAY_SCALE = 2;	
    /* Globals expanded for memory (except _latestStakeId) and compact for storage */	
    struct GlobalsCache {	
        uint256 _lockedSunsTotal;	
        uint256 _nextStakeSharesTotal;	
        uint256 _shareRate;	
        uint256 _stakePenaltyTotal;	
        uint256 _dailyDataCount;	
        uint256 _stakeSharesTotal;	
        uint40 _latestStakeId;	
        uint256 _currentDay;	
    }	
    struct GlobalsStore {	
        uint72 lockedSunsTotal;	
        uint72 nextStakeSharesTotal;	
        uint40 shareRate;	
        uint72 stakePenaltyTotal;	
        uint16 dailyDataCount;	
        uint72 stakeSharesTotal;	
        uint40 latestStakeId;	
    }	
    GlobalsStore public globals;	
    /* Daily data */	
    struct DailyDataStore {	
        uint72 dayPayoutTotal;	
        uint256 dayDividends;	
        uint72 dayStakeSharesTotal;	
    }	
    mapping(uint256 => DailyDataStore) public dailyData;	
    /* Stake expanded for memory (except _stakeId) and compact for storage */	
    struct StakeCache {	
        uint40 _stakeId;	
        uint256 _stakedSuns;	
        uint256 _stakeShares;	
        uint256 _lockedDay;	
        uint256 _stakedDays;	
        uint256 _unlockedDay;	
    }	
    struct StakeStore {	
        uint40 stakeId;	
        uint72 stakedSuns;	
        uint72 stakeShares;	
        uint16 lockedDay;	
        uint16 stakedDays;	
        uint16 unlockedDay;	
    }	
    mapping(address => StakeStore[]) public stakeLists;	
    /* Temporary state for calculating daily rounds */	
    struct DailyRoundState {	
        uint256 _allocSupplyCached;	
        uint256 _payoutTotal;	
    }	
    struct XfLobbyEntryStore {	
        uint96 rawAmount;	
        address referrerAddr;	
    }	
    struct XfLobbyQueueStore {	
        uint40 headIndex;	
        uint40 tailIndex;	
        mapping(uint256 => XfLobbyEntryStore) entries;	
    }	
    mapping(uint256 => uint256) public xfLobby;	
    mapping(uint256 => mapping(address => XfLobbyQueueStore)) public xfLobbyMembers;	
    /**	
     * @dev PUBLIC FACING: Optionally update daily data for a smaller	
     * range to reduce gas cost for a subsequent operation	
     * @param beforeDay Only update days before this day number (optional; 0 for current day)	
     */	
    function dailyDataUpdate(uint256 beforeDay)	
        external	
    {	
        GlobalsCache memory g;	
        GlobalsCache memory gSnapshot;	
        _globalsLoad(g, gSnapshot);	
        /* Skip pre-claim period */	
        require(g._currentDay > CLAIM_PHASE_START_DAY, "CSNE: Too early");	
        if (beforeDay != 0) {	
            require(beforeDay <= g._currentDay, "CSNE: beforeDay cannot be in the future");	
            _dailyDataUpdate(g, beforeDay, false);	
        } else {	
            /* Default to updating before current day */	
            _dailyDataUpdate(g, g._currentDay, false);	
        }	
        _globalsSync(g, gSnapshot);	
    }	
    /**	
     * @dev PUBLIC FACING: External helper to return multiple values of daily data with	
     * a single call.	
     * @param endDay Last day (non-inclusive) of data range	
     * @param beginDay First day of data range	
     * @return array of day stake shares total	
     * @return array of day payout total	
     */	
    function dailyDataRange(uint256 beginDay, uint256 endDay)	
        external	
        view	
        returns (uint256[] memory _dayStakeSharesTotal, uint256[] memory _dayPayoutTotal, uint256[] memory _dayDividends)	
    {	
        require(beginDay < endDay && endDay <= globals.dailyDataCount, "CSNE: range invalid");	
        _dayStakeSharesTotal = new uint256[](endDay - beginDay);	
        _dayPayoutTotal = new uint256[](endDay - beginDay);	
        _dayDividends = new uint256[](endDay - beginDay);	
        uint256 src = beginDay;	
        uint256 dst = 0;	
        do {	
            _dayStakeSharesTotal[dst] = uint256(dailyData[src].dayStakeSharesTotal);	
            _dayPayoutTotal[dst++] = uint256(dailyData[src].dayPayoutTotal);	
            _dayDividends[dst++] = dailyData[src].dayDividends;	
        } while (++src < endDay);	
        return (_dayStakeSharesTotal, _dayPayoutTotal, _dayDividends);	
    }	
    /**	
     * @dev PUBLIC FACING: External helper to return most global info with a single call.	
     * Ugly implementation due to limitations of the standard ABI encoder.	
     * @return Fixed array of values	
     */	
    function globalInfo()	
        external	
        view	
        returns (uint256[10] memory)	
    {	
        return [	
            globals.lockedSunsTotal,	
            globals.nextStakeSharesTotal,	
            globals.shareRate,	
            globals.stakePenaltyTotal,	
            globals.dailyDataCount,	
            globals.stakeSharesTotal,	
            globals.latestStakeId,	
            block.timestamp,	
            totalSupply(),	
            xfLobby[_currentDay()]	
        ];	
    }	
    /**	
     * @dev PUBLIC FACING: ERC20 totalSupply() is the circulating supply and does not include any	
     * staked Suns. allocatedSupply() includes both.	
     * @return Allocated Supply in Suns	
     */	
    function allocatedSupply()	
        external	
        view	
        returns (uint256)	
    {	
        return totalSupply() + globals.lockedSunsTotal;	
    }	
    /**	
     * @dev PUBLIC FACING: External helper for the current day number since launch time	
     * @return Current day number (zero-based)	
     */	
    function currentDay()	
        external	
        view	
        returns (uint256)	
    {	
        return _currentDay();	
    }	
    function _currentDay()	
        internal	
        view	
        returns (uint256)	
    {	
        if (block.timestamp < LAUNCH_TIME){	
             return 0;	
        }else{	
             return (block.timestamp - LAUNCH_TIME) / 1 days;	
        }	
    }	
    function _dailyDataUpdateAuto(GlobalsCache memory g)	
        internal	
    {	
        _dailyDataUpdate(g, g._currentDay, true);	
    }	
    function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot)	
        internal	
        view	
    {	
        g._lockedSunsTotal = globals.lockedSunsTotal;	
        g._nextStakeSharesTotal = globals.nextStakeSharesTotal;	
        g._shareRate = globals.shareRate;	
        g._stakePenaltyTotal = globals.stakePenaltyTotal;	
        g._dailyDataCount = globals.dailyDataCount;	
        g._stakeSharesTotal = globals.stakeSharesTotal;	
        g._latestStakeId = globals.latestStakeId;	
        g._currentDay = _currentDay();	
        _globalsCacheSnapshot(g, gSnapshot);	
    }	
    function _globalsCacheSnapshot(GlobalsCache memory g, GlobalsCache memory gSnapshot)	
        internal	
        pure	
    {	
        gSnapshot._lockedSunsTotal = g._lockedSunsTotal;	
        gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal;	
        gSnapshot._shareRate = g._shareRate;	
        gSnapshot._stakePenaltyTotal = g._stakePenaltyTotal;	
        gSnapshot._dailyDataCount = g._dailyDataCount;	
        gSnapshot._stakeSharesTotal = g._stakeSharesTotal;	
        gSnapshot._latestStakeId = g._latestStakeId;	
    }	
    function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot)	
        internal	
    {	
        if (g._lockedSunsTotal != gSnapshot._lockedSunsTotal	
            || g._nextStakeSharesTotal != gSnapshot._nextStakeSharesTotal	
            || g._shareRate != gSnapshot._shareRate	
            || g._stakePenaltyTotal != gSnapshot._stakePenaltyTotal) {	
            globals.lockedSunsTotal = uint72(g._lockedSunsTotal);	
            globals.nextStakeSharesTotal = uint72(g._nextStakeSharesTotal);	
            globals.shareRate = uint40(g._shareRate);	
            globals.stakePenaltyTotal = uint72(g._stakePenaltyTotal);	
        }	
        if (g._dailyDataCount != gSnapshot._dailyDataCount	
            || g._stakeSharesTotal != gSnapshot._stakeSharesTotal	
            || g._latestStakeId != gSnapshot._latestStakeId) {	
            globals.dailyDataCount = uint16(g._dailyDataCount);	
            globals.stakeSharesTotal = uint72(g._stakeSharesTotal);	
            globals.latestStakeId = g._latestStakeId;	
        }	
    }	
    function _stakeLoad(StakeStore storage stRef, uint40 stakeIdParam, StakeCache memory st)	
        internal	
        view	
    {	
        /* Ensure caller's stakeIndex is still current */	
        require(stakeIdParam == stRef.stakeId, "CSNE: stakeIdParam not in stake");	
        st._stakeId = stRef.stakeId;	
        st._stakedSuns = stRef.stakedSuns;	
        st._stakeShares = stRef.stakeShares;	
        st._lockedDay = stRef.lockedDay;	
        st._stakedDays = stRef.stakedDays;	
        st._unlockedDay = stRef.unlockedDay;	
    }	
    function _stakeUpdate(StakeStore storage stRef, StakeCache memory st)	
        internal	
    {	
        stRef.stakeId = st._stakeId;	
        stRef.stakedSuns = uint72(st._stakedSuns);	
        stRef.stakeShares = uint72(st._stakeShares);	
        stRef.lockedDay = uint16(st._lockedDay);	
        stRef.stakedDays = uint16(st._stakedDays);	
        stRef.unlockedDay = uint16(st._unlockedDay);	
    }	
    function _stakeAdd(	
        StakeStore[] storage stakeListRef,	
        uint40 newStakeId,	
        uint256 newStakedSuns,	
        uint256 newStakeShares,	
        uint256 newLockedDay,	
        uint256 newStakedDays	
    )	
        internal	
    {	
        stakeListRef.push(	
            StakeStore(	
                newStakeId,	
                uint72(newStakedSuns),	
                uint72(newStakeShares),	
                uint16(newLockedDay),	
                uint16(newStakedDays),	
                uint16(0) // unlockedDay	
            )	
        );	
    }	
    /**	
     * @dev Efficiently delete from an unordered array by moving the last element	
     * to the "hole" and reducing the array length. Can change the order of the list	
     * and invalidate previously held indexes.	
     * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd()	
     * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage	
     * @param stakeIndex Index of the element to delete	
     */	
    function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex)	
        internal	
    {	
        uint256 lastIndex = stakeListRef.length - 1;	
        /* Skip the copy if element to be removed is already the last element */	
        if (stakeIndex != lastIndex) {	
            /* Copy last element to the requested element's "hole" */	
            stakeListRef[stakeIndex] = stakeListRef[lastIndex];	
        }	
        /*	
            Reduce the array length now that the array is contiguous.	
            Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex'	
        */	
        stakeListRef.pop();	
    }	
    /**	
     * @dev Estimate the stake payout for an incomplete day	
     * @param g Cache of stored globals	
     * @param stakeSharesParam Param from stake to calculate bonuses for	
     * @param day Day to calculate bonuses for	
     * @return Payout in Suns	
     */	
    function _estimatePayoutRewardsDay(GlobalsCache memory g, uint256 stakeSharesParam, uint256 day)	
        internal	
        view	
        returns (uint256 payout)	
    {	
        /* Prevent updating state for this estimation */	
        GlobalsCache memory gTmp;	
        _globalsCacheSnapshot(g, gTmp);	
        DailyRoundState memory rs;	
        rs._allocSupplyCached = totalSupply() + g._lockedSunsTotal;	
        _dailyRoundCalc(gTmp, rs, day);	
        /* Stake is no longer locked so it must be added to total as if it were */	
        gTmp._stakeSharesTotal += stakeSharesParam;	
        payout = rs._payoutTotal * stakeSharesParam / gTmp._stakeSharesTotal;	
        return payout;	
    }	
    function _dailyRoundCalc(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)	
        private	
        view	
    
        {	
        rs._payoutTotal = (rs._allocSupplyCached * 50000 / 68854153);	
        if (g._stakePenaltyTotal != 0) {	
            rs._payoutTotal += g._stakePenaltyTotal;	
            g._stakePenaltyTotal = 0;	
        }	
    }	
    function _dailyRoundCalcAndStore(GlobalsCache memory g, DailyRoundState memory rs, uint256 day)	
        private	
    {	
        _dailyRoundCalc(g, rs, day);	
        dailyData[day].dayPayoutTotal = uint72(rs._payoutTotal);	
        dailyData[day].dayDividends = xfLobby[day];	
        dailyData[day].dayStakeSharesTotal = uint72(g._stakeSharesTotal);	
    }	
    function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay, bool isAutoUpdate)	
        private	
    {	
        if (g._dailyDataCount >= beforeDay) {	
            /* Already up-to-date */	
            return;	
        }	
        DailyRoundState memory rs;	
        rs._allocSupplyCached = totalSupply() + g._lockedSunsTotal;	
        uint256 day = g._dailyDataCount;	
        _dailyRoundCalcAndStore(g, rs, day);	
        /* Stakes started during this day are added to the total the next day */	
        if (g._nextStakeSharesTotal != 0) {	
            g._stakeSharesTotal += g._nextStakeSharesTotal;	
            g._nextStakeSharesTotal = 0;	
        }	
        while (++day < beforeDay) {	
            _dailyRoundCalcAndStore(g, rs, day);	
        }	
        emit DailyDataUpdate(	
            msg.sender,	
            block.timestamp,	
            g._dailyDataCount, 	
            day	
        );	
        	
        g._dailyDataCount = day;	
    }	
}
function dailyDataUpdate(uint256 beforeDay)	
        external	
    {	
        GlobalsCache memory g;	
        GlobalsCache memory gSnapshot;	
        _globalsLoad(g, gSnapshot);	
        /* Skip pre-claim period */	
        require(g._currentDay > CLAIM_PHASE_START_DAY, "CSNE: Too early");	
        if (beforeDay != 0) {	
            require(beforeDay <= g._currentDay, "CSNE: beforeDay cannot be in the future");	
            _dailyDataUpdate(g, beforeDay, false);	
        } else {	
            /* Default to updating before current day */	
            _dailyDataUpdate(g, g._currentDay, false);	
        }	
        _globalsSync(g, gSnapshot);	
    }
/**	
 * @dev PUBLIC FACING: Optionally update daily data for a smaller	
 * range to reduce gas cost for a subsequent operation	
 * @param beforeDay Only update days before this day number (optional; 0 for current day)	
 */