ETH Price: $2,438.20 (+8.37%)

Transaction Decoder

Block:
15645247 at Sep-30-2022 09:26:59 AM +UTC
Transaction Fee:
0.00136221834856205 ETH $3.32
Gas Used:
138,410 Gas / 9.841907005 Gwei

Emitted Events:

66 BrewlabsLockup.Deposit( user=[Sender] 0xe41de34a6dd5a9367bcef38c8ad0694d61dd6e34, stakeType=2, amount=5095726093135391623946786151 )

Account State Difference:

  Address   Before After State Difference Code
(Coinbase: MEV Builder)
0.009921788838977411 Eth0.010198608838977411 Eth0.00027682
0xb04BA384...c43b68cbF
0xE1f1dd01...B949BB16F 2.39466966401577549 Eth2.39555966401577549 Eth0.00089
0xe41de34A...D61DD6e34
0.004665307672038176 Eth
Nonce: 49
0.002413089323476126 Eth
Nonce: 50
0.00225221834856205

Execution Trace

ETH 0.00089 BrewlabsLockup.compoundReward( _stakeType=2 )
  • ETH 0.00089 0xe1f1dd010bbc2860f81c8f90ea4e38db949bb16f.CALL( )
  • GroveToken.balanceOf( account=0xb04BA3845D2C59f2B71544E426F9c37c43b68cbF ) => ( 1153930742065947241039513891807641 )
    File 1 of 2: BrewlabsLockup
     
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
    import '@openzeppelin/contracts/access/Ownable.sol';
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    import "./libs/IUniRouter02.sol";
    import "./libs/IWETH.sol";
    interface IToken {
         /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the token decimals.
         */
        function decimals() external view returns (uint8);
        /**
         * @dev Returns the token symbol.
         */
        function symbol() external view returns (string memory);
        /**
         * @dev Returns the token name.
         */
        function name() external view returns (string memory);
    }
    contract BrewlabsLockup is Ownable, ReentrancyGuard {
        using SafeERC20 for IERC20;
        // Whether it is initialized
        bool public isInitialized;
        uint256 public duration = 365; // 365 days
        // Whether a limit is set for users
        bool public hasUserLimit;
        // The pool limit (0 if none)
        uint256 public poolLimitPerUser;
        // The block number when staking starts.
        uint256 public startBlock;
        // The block number when staking ends.
        uint256 public bonusEndBlock;
        // swap router and path, slipPage
        uint256 public slippageFactor = 800; // 20% default slippage tolerance
        uint256 public constant slippageFactorUL = 995;
        address public uniRouterAddress;
        address[] public reflectionToStakedPath;
        address[] public earnedToStakedPath;
        address public walletA;
        address public buyBackWallet = 0xE1f1dd010BBC2860F81c8F90Ea4E38dB949BB16F;
        uint256 public performanceFee = 0.00089 ether;
        // The precision factor
        uint256 public PRECISION_FACTOR;
        uint256 public PRECISION_FACTOR_REFLECTION;
        // The staked token
        IERC20 public stakingToken;
        // The earned token
        IERC20 public earnedToken;
        // The dividend token of staking token
        address public dividendToken;
        // Accrued token per share
        uint256 public accDividendPerShare;
        uint256 public totalStaked;
        uint256 private totalEarned;
        uint256 private totalReflections;
        uint256 private reflections;
        uint256 private paidRewards;
        uint256 private shouldTotalPaid;
        struct Lockup {
            uint8 stakeType;
            uint256 duration;
            uint256 depositFee;
            uint256 withdrawFee;
            uint256 rate;
            uint256 accTokenPerShare;
            uint256 lastRewardBlock;
            uint256 totalStaked;
            uint256 totalStakedLimit;
        }
        struct UserInfo {
            uint256 amount; // How many staked tokens the user has provided
            uint256 locked;
            uint256 available;
        }
        struct Stake {
            uint8   stakeType;
            uint256 amount;     // amount to stake
            uint256 duration;   // the lockup duration of the stake
            uint256 end;        // when does the staking period end
            uint256 rewardDebt; // Reward debt
            uint256 reflectionDebt; // Reflection debt
        }
        uint256 constant MAX_STAKES = 256;
        Lockup[] public lockups;
        mapping(address => Stake[]) public userStakes;
        mapping(address => UserInfo) public userStaked;
        event Deposit(address indexed user, uint256 stakeType, uint256 amount);
        event Withdraw(address indexed user, uint256 stakeType, uint256 amount);
        event EmergencyWithdraw(address indexed user, uint256 amount);
        event AdminTokenRecovered(address tokenRecovered, uint256 amount);
        event NewStartAndEndBlocks(uint256 startBlock, uint256 endBlock);
        event LockupUpdated(uint8 _type, uint256 _duration, uint256 _fee0, uint256 _fee1, uint256 _rate);
        event RewardsStop(uint256 blockNumber);
        event EndBlockUpdated(uint256 blockNumber);
        event UpdatePoolLimit(uint256 poolLimitPerUser, bool hasLimit);
        event ServiceInfoUpadted(address _addr, uint256 _fee);
        event DurationUpdated(uint256 _duration);
        event SetSettings(
            uint256 _slippageFactor,
            address _uniRouter,
            address[] _path0,
            address[] _path1,
            address _walletA
        );
        constructor() {}
        /*
         * @notice Initialize the contract
         * @param _stakingToken: staked token address
         * @param _earnedToken: earned token address
         * @param _dividendToken: reflection token address
         * @param _uniRouter: uniswap router address for swap tokens
         * @param _earnedToStakedPath: swap path to compound (earned -> staking path)
         * @param _reflectionToStakedPath: swap path to compound (reflection -> staking path)
         */
        function initialize(
            IERC20 _stakingToken,
            IERC20 _earnedToken,
            address _dividendToken,
            address _uniRouter,
            address[] memory _earnedToStakedPath,
            address[] memory _reflectionToStakedPath
        ) external onlyOwner {
            require(!isInitialized, "Already initialized");
            // Make this contract initialized
            isInitialized = true;
            stakingToken = _stakingToken;
            earnedToken = _earnedToken;
            dividendToken = _dividendToken;
            walletA = msg.sender;
            uint256 decimalsRewardToken = uint256(IToken(address(earnedToken)).decimals());
            require(decimalsRewardToken < 30, "Must be inferior to 30");
            PRECISION_FACTOR = uint256(10**(40 - decimalsRewardToken));
            uint256 decimalsdividendToken = 18;
            if(address(dividendToken) != address(0x0)) {
                decimalsdividendToken = uint256(IToken(address(dividendToken)).decimals());
                require(decimalsdividendToken < 30, "Must be inferior to 30");
            }
            PRECISION_FACTOR_REFLECTION = uint256(10**(40 - decimalsRewardToken));
            uniRouterAddress = _uniRouter;
            earnedToStakedPath = _earnedToStakedPath;
            reflectionToStakedPath = _reflectionToStakedPath;
        }
        /*
         * @notice Deposit staked tokens and collect reward tokens (if any)
         * @param _amount: amount to withdraw (in earnedToken)
         */
        function deposit(uint256 _amount, uint8 _stakeType) external payable nonReentrant {
            require(startBlock > 0 && startBlock < block.number, "Staking hasn't started yet");
            require(_amount > 0, "Amount should be greator than 0");
            require(_stakeType < lockups.length, "Invalid stake type");
            _transferPerformanceFee();
            _updatePool(_stakeType);
            UserInfo storage user = userStaked[msg.sender];
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            if(lockup.totalStakedLimit > 0) {
                require(lockup.totalStaked < lockup.totalStakedLimit, "Total staked limit exceeded");
                if(lockup.totalStaked + _amount > lockup.totalStakedLimit) {
                    _amount = lockup.totalStakedLimit - lockup.totalStaked;
                }
            }
            uint256 pending = 0;
            uint256 pendingReflection = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                pendingReflection = pendingReflection + (
                    stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION - stake.reflectionDebt
                );
                uint256 _pending = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR - stake.rewardDebt;
                pending = pending + _pending;
                stake.rewardDebt = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR;
                stake.reflectionDebt = stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
            }
            if (pending > 0) {
                require(availableRewardTokens() >= pending, "Insufficient reward tokens");
                earnedToken.safeTransfer(address(msg.sender), pending);
                _updateEarned(pending);
                paidRewards = paidRewards + pending;
            }
            pendingReflection = estimateDividendAmount(pendingReflection);
            if (pendingReflection > 0) {
                _transferToken(dividendToken, msg.sender, pendingReflection);
                totalReflections = totalReflections - pendingReflection;
            }
            uint256 beforeAmount = stakingToken.balanceOf(address(this));
            stakingToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            uint256 afterAmount = stakingToken.balanceOf(address(this));        
            uint256 realAmount = afterAmount - beforeAmount;
            if (hasUserLimit) {
                require(
                    realAmount + user.amount <= poolLimitPerUser,
                    "User amount above limit"
                );
            }
            if (lockup.depositFee > 0) {
                uint256 fee = realAmount * lockup.depositFee / 10000;
                if (fee > 0) {
                    stakingToken.safeTransfer(walletA, fee);
                    realAmount = realAmount - fee;
                }
            }
            
            _addStake(_stakeType, msg.sender, lockup.duration, realAmount);
            user.amount = user.amount + realAmount;
            lockup.totalStaked = lockup.totalStaked + realAmount;
            totalStaked = totalStaked + realAmount;
            emit Deposit(msg.sender, _stakeType, realAmount);
        }
        function _addStake(uint8 _stakeType, address _account, uint256 _duration, uint256 _amount) internal {
            Stake[] storage stakes = userStakes[_account];
            uint256 end = block.timestamp + _duration * 1 days;
            uint256 i = stakes.length;
            require(i < MAX_STAKES, "Max stakes");
            stakes.push(); // grow the array
            // find the spot where we can insert the current stake
            // this should make an increasing list sorted by end
            while (i != 0 && stakes[i - 1].end > end) {
                // shift it back one
                stakes[i] = stakes[i - 1];
                i -= 1;
            }
            
            Lockup storage lockup = lockups[_stakeType];
            // insert the stake
            Stake storage newStake = stakes[i];
            newStake.stakeType = _stakeType;
            newStake.duration = _duration;
            newStake.end = end;
            newStake.amount = _amount;
            newStake.rewardDebt = newStake.amount * lockup.accTokenPerShare / PRECISION_FACTOR;
            newStake.reflectionDebt = newStake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
        }
        /*
         * @notice Withdraw staked tokens and collect reward tokens
         * @param _amount: amount to withdraw (in earnedToken)
         */
        function withdraw(uint256 _amount, uint8 _stakeType) external payable nonReentrant {
            require(_amount > 0, "Amount should be greator than 0");
            require(_stakeType < lockups.length, "Invalid stake type");
            _transferPerformanceFee();
            _updatePool(_stakeType);
            UserInfo storage user = userStaked[msg.sender];
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            
            uint256 pending = 0;
            uint256 pendingReflection = 0;
            uint256 remained = _amount;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                if(remained == 0) break;
                uint256 _pending = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR - stake.rewardDebt;
                pendingReflection = pendingReflection + (
                    stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION - stake.reflectionDebt
                );
                pending = pending + _pending;
                if(stake.end < block.timestamp || bonusEndBlock < block.number) {
                    if(stake.amount > remained) {
                        stake.amount = stake.amount - remained;
                        remained = 0;
                    } else {
                        remained = remained - stake.amount;
                        stake.amount = 0;
                    }
                }
                stake.rewardDebt = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR;
                stake.reflectionDebt = stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
            }
            if (pending > 0) {
                require(availableRewardTokens() >= pending, "Insufficient reward tokens");
                earnedToken.safeTransfer(address(msg.sender), pending);
                _updateEarned(pending);
                paidRewards = paidRewards + pending;
            }
            if (pendingReflection > 0) {
                pendingReflection = estimateDividendAmount(pendingReflection);
                _transferToken(dividendToken, msg.sender, pendingReflection);
                totalReflections = totalReflections - pendingReflection;
            }
            uint256 realAmount = _amount - remained;
            user.amount = user.amount - realAmount;
            lockup.totalStaked = lockup.totalStaked - realAmount;
            totalStaked = totalStaked - realAmount;
            if(realAmount > 0) {
                if (lockup.withdrawFee > 0) {
                    uint256 fee = realAmount * lockup.withdrawFee / 10000;
                    stakingToken.safeTransfer(walletA, fee);
                    realAmount = realAmount - fee;
                }
                stakingToken.safeTransfer(address(msg.sender), realAmount);
            }
            emit Withdraw(msg.sender, _stakeType, realAmount);
        }
        function claimReward(uint8 _stakeType) external payable nonReentrant {
            if(_stakeType >= lockups.length) return;
            if(startBlock == 0) return;
            _transferPerformanceFee();
            _updatePool(_stakeType);
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            uint256 pending = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                uint256 _pending = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR - stake.rewardDebt;
                pending = pending + _pending;
                stake.rewardDebt = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR;
            }
            if (pending > 0) {
                require(availableRewardTokens() >= pending, "Insufficient reward tokens");
                earnedToken.safeTransfer(address(msg.sender), pending);
                _updateEarned(pending);
                paidRewards = paidRewards + pending;
            }
        }
        function claimDividend(uint8 _stakeType) external payable nonReentrant {
            if(_stakeType >= lockups.length) return;
            if(startBlock == 0) return;
            _transferPerformanceFee();
            _updatePool(_stakeType);
            Stake[] storage stakes = userStakes[msg.sender];
            uint256 pendingReflection = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                pendingReflection = pendingReflection + (
                    stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION - stake.reflectionDebt
                );
                stake.reflectionDebt = stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
            }
            pendingReflection = estimateDividendAmount(pendingReflection);
            if (pendingReflection > 0) {
                _transferToken(dividendToken, msg.sender, pendingReflection);
                totalReflections = totalReflections - pendingReflection;
            }
        }
        function compoundReward(uint8 _stakeType) external payable nonReentrant {
            if(_stakeType >= lockups.length) return;
            if(startBlock == 0) return;
            _transferPerformanceFee();
            _updatePool(_stakeType);
            UserInfo storage user = userStaked[msg.sender];
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            uint256 pending = 0;
            uint256 compounded = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                uint256 _pending = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR - stake.rewardDebt;
                pending = pending + _pending;
                if(address(stakingToken) != address(earnedToken) && _pending > 0) {
                    uint256 _beforeAmount = stakingToken.balanceOf(address(this));
                    _safeSwap(_pending, earnedToStakedPath, address(this));
                    uint256 _afterAmount = stakingToken.balanceOf(address(this));
                    _pending = _afterAmount - _beforeAmount;
                }
                compounded = compounded + _pending;
                stake.amount = stake.amount + _pending;
                stake.rewardDebt = stake.amount * lockup.accTokenPerShare / PRECISION_FACTOR;
                stake.reflectionDebt = stake.reflectionDebt + _pending * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
            }
            if (pending > 0) {
                require(availableRewardTokens() >= pending, "Insufficient reward tokens");
                _updateEarned(pending);
                paidRewards = paidRewards + pending;
                user.amount = user.amount + compounded;
                lockup.totalStaked = lockup.totalStaked + compounded;
                totalStaked = totalStaked + compounded;
                emit Deposit(msg.sender, _stakeType, compounded);
            }
        }
        function compoundDividend(uint8 _stakeType) external payable nonReentrant {
            if(_stakeType >= lockups.length) return;
            if(startBlock == 0) return;
            _transferPerformanceFee();
            _updatePool(_stakeType);
            UserInfo storage user = userStaked[msg.sender];
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            uint256 compounded = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                uint256 _pending = stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION - stake.reflectionDebt;
                _pending = estimateDividendAmount(_pending);
                totalReflections = totalReflections - _pending;
                if(address(stakingToken) != address(dividendToken) && _pending > 0) {
                    if(address(dividendToken) == address(0x0)) {
                        address wethAddress = IUniRouter02(uniRouterAddress).WETH();
                        IWETH(wethAddress).deposit{ value: _pending }();
                    }
                    uint256 _beforeAmount = stakingToken.balanceOf(address(this));
                    _safeSwap(_pending, reflectionToStakedPath, address(this));
                    uint256 _afterAmount = stakingToken.balanceOf(address(this));
                    _pending = _afterAmount - _beforeAmount;
                }
                
                compounded = compounded + _pending;
                stake.amount = stake.amount + _pending;
                stake.rewardDebt = stake.rewardDebt + _pending * lockup.accTokenPerShare / PRECISION_FACTOR;
                stake.reflectionDebt = stake.amount * accDividendPerShare / PRECISION_FACTOR_REFLECTION;
            }
            if (compounded > 0) {
                user.amount = user.amount + compounded;
                lockup.totalStaked = lockup.totalStaked + compounded;
                totalStaked = totalStaked + compounded;
                emit Deposit(msg.sender, _stakeType, compounded);
            }
        }
        function _transferPerformanceFee() internal {
            require(msg.value >= performanceFee, 'should pay small gas to compound or harvest');
            payable(buyBackWallet).transfer(performanceFee);
            if(msg.value > performanceFee) {
                payable(msg.sender).transfer(msg.value - performanceFee);
            }
        }
        /*
         * @notice Withdraw staked tokens without caring about rewards
         * @dev Needs to be for emergency.
         */
        function emergencyWithdraw(uint8 _stakeType) external nonReentrant {
            if(_stakeType >= lockups.length) return;
            UserInfo storage user = userStaked[msg.sender];
            Stake[] storage stakes = userStakes[msg.sender];
            Lockup storage lockup = lockups[_stakeType];
            uint256 amountToTransfer = 0;
            for(uint256 j = 0; j < stakes.length; j++) {
                Stake storage stake = stakes[j];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                amountToTransfer = amountToTransfer + stake.amount;
                stake.amount = 0;
                stake.rewardDebt = 0;
                stake.reflectionDebt = 0;
            }
            if (amountToTransfer > 0) {
                stakingToken.safeTransfer(address(msg.sender), amountToTransfer);
                user.amount = user.amount - amountToTransfer;
                lockup.totalStaked = lockup.totalStaked - amountToTransfer;
                totalStaked = totalStaked - amountToTransfer;
            }
            emit EmergencyWithdraw(msg.sender, amountToTransfer);
        }
        function rewardPerBlock(uint8 _stakeType) external view returns (uint256) {
            if(_stakeType >= lockups.length) return 0;
            return lockups[_stakeType].rate;
        }
        /**
         * @notice Available amount of reward token
         */
        function availableRewardTokens() public view returns (uint256) {
            if(address(earnedToken) == address(dividendToken)) return totalEarned;
            uint256 _amount = earnedToken.balanceOf(address(this));
            if (address(earnedToken) == address(stakingToken)) {
                if (_amount < totalStaked) return 0;
                return _amount - totalStaked;
            }
            return _amount;
        }
        /**
         * @notice Available amount of reflection token
         */
        function availableDividendTokens() public view returns (uint256) {
            if(address(dividendToken) == address(0x0)) {
                return address(this).balance;
            }
            uint256 _amount = IERC20(dividendToken).balanceOf(address(this));
            
            if(address(dividendToken) == address(earnedToken)) {
                if(_amount < totalEarned) return 0;
                _amount = _amount - totalEarned;
            }
            if(address(dividendToken) == address(stakingToken)) {
                if(_amount < totalStaked) return 0;
                _amount = _amount - totalStaked;
            }
            return _amount;
        }
        function insufficientRewards() external view returns (uint256) {
            uint256 adjustedShouldTotalPaid = shouldTotalPaid;
            uint256 remainRewards = availableRewardTokens() + paidRewards;
            for(uint i = 0; i < lockups.length; i++) {
                if(startBlock == 0) {
                    adjustedShouldTotalPaid = adjustedShouldTotalPaid + lockups[i].rate * duration * 6426;
                } else {
                    uint256 remainBlocks = _getMultiplier(lockups[i].lastRewardBlock, bonusEndBlock);
                    adjustedShouldTotalPaid = adjustedShouldTotalPaid + lockups[i].rate * remainBlocks;
                }
            }
            if(remainRewards >= adjustedShouldTotalPaid) return 0;
            return adjustedShouldTotalPaid - remainRewards;
        }
        function userInfo(uint8 _stakeType, address _account) external view returns (uint256 amount, uint256 available, uint256 locked) {
            Stake[] memory stakes = userStakes[_account];
            
            for(uint256 i = 0; i < stakes.length; i++) {
                Stake memory stake = stakes[i];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                
                amount = amount + stake.amount;
                if(block.timestamp > stake.end || bonusEndBlock < block.number) {
                    available = available + stake.amount;
                } else {
                    locked = locked + stake.amount;
                }
            }
        }
        /*
         * @notice View function to see pending reward on frontend.
         * @param _user: user address
         * @return Pending reward for a given user
         */
        function pendingReward(address _account, uint8 _stakeType) external view returns (uint256) {
            if(_stakeType >= lockups.length || startBlock == 0) return 0;
            Stake[] memory stakes = userStakes[_account];
            Lockup memory lockup = lockups[_stakeType];
            if(lockup.totalStaked == 0) return 0;
            
            uint256 adjustedTokenPerShare = lockup.accTokenPerShare;
            if (block.number > lockup.lastRewardBlock && lockup.totalStaked != 0 && lockup.lastRewardBlock > 0) {
                uint256 multiplier = _getMultiplier(lockup.lastRewardBlock, block.number);
                uint256 reward = multiplier * lockup.rate;
                adjustedTokenPerShare = lockup.accTokenPerShare + reward * PRECISION_FACTOR / lockup.totalStaked;
            }
            uint256 pending = 0;
            for(uint256 i = 0; i < stakes.length; i++) {
                Stake memory stake = stakes[i];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                pending = pending + (
                        stake.amount * adjustedTokenPerShare / PRECISION_FACTOR - stake.rewardDebt
                    );
            }
            return pending;
        }
        function pendingDividends(address _account, uint8 _stakeType) external view returns (uint256) {
            if(_stakeType >= lockups.length) return 0;
            if(startBlock == 0 || totalStaked == 0) return 0;
            Stake[] memory stakes = userStakes[_account];
            
            uint256 reflectionAmount = availableDividendTokens();
            if(reflectionAmount < totalReflections) {
                reflectionAmount = totalReflections;
            }
            uint256 sTokenBal = totalStaked;
            uint256 eTokenBal = availableRewardTokens();
            if(address(stakingToken) == address(earnedToken)) {
                sTokenBal = sTokenBal + eTokenBal;
            }
            uint256 adjustedReflectionPerShare = accDividendPerShare + ( 
                (reflectionAmount - totalReflections) * PRECISION_FACTOR_REFLECTION / sTokenBal
            );
            
            uint256 pendingReflection = 0;
            for(uint256 i = 0; i < stakes.length; i++) {
                Stake memory stake = stakes[i];
                if(stake.stakeType != _stakeType) continue;
                if(stake.amount == 0) continue;
                pendingReflection = pendingReflection + (
                    stake.amount * adjustedReflectionPerShare / PRECISION_FACTOR_REFLECTION - stake.reflectionDebt
                );
            }
            return pendingReflection;
        }
        /************************
        ** Admin Methods
        *************************/
        function harvest() external onlyOwner {
            _updatePool(0);
            reflections = estimateDividendAmount(reflections);
            if(reflections > 0) {
                _transferToken(dividendToken, walletA, reflections);
                totalReflections = totalReflections - reflections;
                reflections = 0;
            }
        }
        /*
         * @notice Deposit reward token
         * @dev Only call by owner. Needs to be for deposit of reward token when reflection token is same with reward token.
         */
        function depositRewards(uint _amount) external onlyOwner nonReentrant {
            require(_amount > 0, "invalid amount");
            uint256 beforeAmt = earnedToken.balanceOf(address(this));
            earnedToken.safeTransferFrom(msg.sender, address(this), _amount);
            uint256 afterAmt = earnedToken.balanceOf(address(this));
            totalEarned = totalEarned + afterAmt - beforeAmt;
        }
        function increaseEmissionRate(uint8 _stakeType, uint256 _amount) external onlyOwner {
            require(startBlock > 0, "pool is not started");
            require(bonusEndBlock > block.number, "pool was already finished");
            require(_amount > 0, "invalid amount");
            
            _updatePool(_stakeType);
            uint256 beforeAmt = earnedToken.balanceOf(address(this));
            earnedToken.safeTransferFrom(msg.sender, address(this), _amount);
            uint256 afterAmt = earnedToken.balanceOf(address(this));
            totalEarned = totalEarned + afterAmt - beforeAmt;
            uint256 remainRewards = availableRewardTokens() + paidRewards;
            uint256 adjustedShouldTotalPaid = shouldTotalPaid;
            for(uint i = 0; i < lockups.length; i++) {
                if(i == _stakeType) continue;
                if(startBlock == 0) {
                    adjustedShouldTotalPaid = adjustedShouldTotalPaid + lockups[i].rate * duration * 6426;
                } else {
                    uint256 remainBlocks = _getMultiplier(lockups[i].lastRewardBlock, bonusEndBlock);
                    adjustedShouldTotalPaid = adjustedShouldTotalPaid + lockups[i].rate * remainBlocks;
                }
            }
            if(remainRewards > shouldTotalPaid) {
                remainRewards = remainRewards - adjustedShouldTotalPaid;
                uint256 remainBlocks = bonusEndBlock - block.number;
                lockups[_stakeType].rate = remainRewards / remainBlocks;
                emit LockupUpdated(_stakeType, lockups[_stakeType].duration, lockups[_stakeType].depositFee, lockups[_stakeType].withdrawFee, lockups[_stakeType].rate);
            }
        }
        /*
         * @notice Withdraw reward token
         * @dev Only callable by owner. Needs to be for emergency.
         */
        function emergencyRewardWithdraw(uint256 _amount) external onlyOwner {
            require( block.number > bonusEndBlock, "Pool is running");
            require(availableRewardTokens() >= _amount, "Insufficient reward tokens");
            earnedToken.safeTransfer(address(msg.sender), _amount);        
            if (totalEarned > 0) {
                if (_amount > totalEarned) {
                    totalEarned = 0;
                } else {
                    totalEarned = totalEarned - _amount;
                }
            }
        }
        /**
         * @notice It allows the admin to recover wrong tokens sent to the contract
         * @param _tokenAddress: the address of the token to withdraw
         * @param _tokenAmount: the number of tokens to withdraw
         * @dev This function is only callable by admin.
         */
        function recoverWrongTokens(address _tokenAddress, uint256 _tokenAmount) external onlyOwner {
            require(
                _tokenAddress != address(earnedToken),
                "Cannot be reward token"
            );
            if(_tokenAddress == address(stakingToken)) {
                uint256 tokenBal = stakingToken.balanceOf(address(this));
                require(_tokenAmount <= tokenBal - totalStaked, "Insufficient balance");
            }
            if(_tokenAddress == address(0x0)) {
                payable(msg.sender).transfer(_tokenAmount);
            } else {
                IERC20(_tokenAddress).safeTransfer(address(msg.sender), _tokenAmount);
            }
            emit AdminTokenRecovered(_tokenAddress, _tokenAmount);
        }
        function startReward() external onlyOwner {
            require(startBlock == 0, "Pool was already started");
            startBlock = block.number + 100;
            bonusEndBlock = startBlock + duration * 6426;
            for(uint256 i = 0; i < lockups.length; i++) {
                lockups[i].lastRewardBlock = startBlock;
            }
            
            emit NewStartAndEndBlocks(startBlock, bonusEndBlock);
        }
        function stopReward() external onlyOwner {
            for(uint8 i = 0; i < lockups.length; i++) {
                _updatePool(i);
            }
            uint256 remainRewards = availableRewardTokens() + paidRewards;
            if(remainRewards > shouldTotalPaid) {
                remainRewards = remainRewards - shouldTotalPaid;
                earnedToken.transfer(msg.sender, remainRewards);
                _updateEarned(remainRewards);
            }
            bonusEndBlock = block.number;
            emit RewardsStop(bonusEndBlock);
        }
        function updateEndBlock(uint256 _endBlock) external onlyOwner {
            require(startBlock > 0, "Pool is not started");
            require(bonusEndBlock > block.number, "Pool was already finished");
            require(_endBlock > block.number && _endBlock > startBlock, "Invalid end block");
            bonusEndBlock = _endBlock;
            emit EndBlockUpdated(_endBlock);
        }
        /*
         * @notice Update pool limit per user
         * @dev Only callable by owner.
         * @param _hasUserLimit: whether the limit remains forced
         * @param _poolLimitPerUser: new pool limit per user
         */
        function updatePoolLimitPerUser( bool _hasUserLimit, uint256 _poolLimitPerUser) external onlyOwner {
            if (_hasUserLimit) {
                require(
                    _poolLimitPerUser > poolLimitPerUser,
                    "New limit must be higher"
                );
                poolLimitPerUser = _poolLimitPerUser;
            } else {
                poolLimitPerUser = 0;
            }
            hasUserLimit = _hasUserLimit;
            emit UpdatePoolLimit(poolLimitPerUser, _hasUserLimit);
        }
        function updateLockup(uint8 _stakeType, uint256 _duration, uint256 _depositFee, uint256 _withdrawFee, uint256 _rate, uint256 _totalStakedLimit) external onlyOwner {
            // require(block.number < startBlock, "Pool was already started");
            require(_stakeType < lockups.length, "Lockup Not found");
            require(_depositFee < 2000, "Invalid deposit fee");
            require(_withdrawFee < 2000, "Invalid withdraw fee");
            _updatePool(_stakeType);
            Lockup storage _lockup = lockups[_stakeType];
            _lockup.duration = _duration;
            _lockup.depositFee = _depositFee;
            _lockup.withdrawFee = _withdrawFee;
            _lockup.rate = _rate;
            _lockup.totalStakedLimit = _totalStakedLimit;
            
            emit LockupUpdated(_stakeType, _duration, _depositFee, _withdrawFee, _rate);
        }
        function addLockup(uint256 _duration, uint256 _depositFee, uint256 _withdrawFee, uint256 _rate, uint256 _totalStakedLimit) external onlyOwner {
            require(_depositFee < 2000, "Invalid deposit fee");
            require(_withdrawFee < 2000, "Invalid withdraw fee");
            
            lockups.push();
            
            Lockup storage _lockup = lockups[lockups.length - 1];
            _lockup.duration = _duration;
            _lockup.depositFee = _depositFee;
            _lockup.withdrawFee = _withdrawFee;
            _lockup.rate = _rate;
            _lockup.lastRewardBlock = block.number;
            _lockup.totalStakedLimit = _totalStakedLimit;
            emit LockupUpdated(uint8(lockups.length - 1), _duration, _depositFee, _withdrawFee, _rate);
        }
        function setServiceInfo(address _addr, uint256 _fee) external {
            require(msg.sender == buyBackWallet, "setServiceInfo: FORBIDDEN");
            require(_addr != address(0x0), "Invalid address");
            require(_fee < 0.05 ether, "fee cannot exceed 0.05 ether");
            buyBackWallet = _addr;
            performanceFee = _fee;
            emit ServiceInfoUpadted(_addr, _fee);
        }
        
        function setDuration(uint256 _duration) external onlyOwner {
            require(startBlock == 0, "Pool was already started");
            require(_duration >= 30, "lower limit reached");
            duration = _duration;
            emit DurationUpdated(_duration);
        }
        function setSettings(
            uint256 _slippageFactor, 
            address _uniRouter, 
            address[] memory _earnedToStakedPath, 
            address[] memory _reflectionToStakedPath,
            address _feeAddr
        ) external onlyOwner {
            require(_slippageFactor <= slippageFactorUL, "_slippageFactor too high");
            require(_feeAddr != address(0x0), "Invalid Address");
            slippageFactor = _slippageFactor;
            uniRouterAddress = _uniRouter;
            reflectionToStakedPath = _reflectionToStakedPath;
            earnedToStakedPath = _earnedToStakedPath;
            walletA = _feeAddr;
            emit SetSettings(_slippageFactor, _uniRouter, _earnedToStakedPath, _reflectionToStakedPath, _feeAddr);
        }
        /************************
        ** Internal Methods
        *************************/
        /*
         * @notice Update reward variables of the given pool to be up-to-date.
         */
        function _updatePool(uint8 _stakeType) internal {
            // calc reflection rate
            if(totalStaked > 0) {
                uint256 reflectionAmount = availableDividendTokens();
                if(reflectionAmount < totalReflections) {
                    reflectionAmount = totalReflections;
                }
                uint256 sTokenBal = totalStaked;
                uint256 eTokenBal = availableRewardTokens();
                if(address(stakingToken) == address(earnedToken)) {
                    sTokenBal = sTokenBal + eTokenBal;
                }
                accDividendPerShare = accDividendPerShare + (
                    (reflectionAmount - totalReflections) * PRECISION_FACTOR_REFLECTION / sTokenBal
                );
                if(address(stakingToken) == address(earnedToken)) {
                    reflections = reflections + (reflectionAmount - totalReflections) * eTokenBal / sTokenBal;
                }
                totalReflections = reflectionAmount;
            }
            Lockup storage lockup = lockups[_stakeType];
            if (block.number <= lockup.lastRewardBlock || lockup.lastRewardBlock == 0) return;
            if (lockup.totalStaked == 0) {
                lockup.lastRewardBlock = block.number;
                return;
            }
            uint256 multiplier = _getMultiplier(lockup.lastRewardBlock, block.number);
            uint256 _reward = multiplier * lockup.rate;
            lockup.accTokenPerShare = lockup.accTokenPerShare + (
                _reward * PRECISION_FACTOR / lockup.totalStaked
            );
            lockup.lastRewardBlock = block.number;
            shouldTotalPaid = shouldTotalPaid + _reward;
        }
        function estimateDividendAmount(uint256 amount) internal view returns(uint256) {
            uint256 dTokenBal = availableDividendTokens();
            if(amount > totalReflections) amount = totalReflections;
            if(amount > dTokenBal) amount = dTokenBal;
            return amount;
        }
        /*
         * @notice Return reward multiplier over the given _from to _to block.
         * @param _from: block to start
         * @param _to: block to finish
         */
        function _getMultiplier(uint256 _from, uint256 _to)
            internal
            view
            returns (uint256)
        {
            if (_to <= bonusEndBlock) {
                return _to - _from;
            } else if (_from >= bonusEndBlock) {
                return 0;
            } else {
                return bonusEndBlock - _from;
            }
        }
        function _transferToken(address _token, address _to, uint256 _amount) internal {
            if(_token == address(0x0)) {
                payable(_to).transfer(_amount);
            } else {
                IERC20(_token).transfer(_to, _amount);
            }
        }
        function _updateEarned(uint256 _amount) internal {
            if(totalEarned > _amount) {
                totalEarned = totalEarned - _amount;
            } else {
                totalEarned = 0;
            }
        }
        function _safeSwap(
            uint256 _amountIn,
            address[] memory _path,
            address _to
        ) internal {
            uint256[] memory amounts = IUniRouter02(uniRouterAddress).getAmountsOut(_amountIn, _path);
            uint256 amountOut = amounts[amounts.length - 1];
            IERC20(_path[0]).safeApprove(uniRouterAddress, _amountIn);
            IUniRouter02(uniRouterAddress).swapExactTokensForTokensSupportingFeeOnTransferTokens(
                _amountIn,
                amountOut * slippageFactor / 1000,
                _path,
                _to,
                block.timestamp + 600
            );
        }
        receive() external payable {}
    }// SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
    pragma solidity ^0.8.0;
    import "../IERC20.sol";
    import "../../../utils/Address.sol";
    /**
     * @title SafeERC20
     * @dev Wrappers around ERC20 operations that throw on failure (when the token
     * contract returns false). Tokens that return no value (and instead revert or
     * throw on failure) are also supported, non-reverting calls are assumed to be
     * successful.
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
     */
    library SafeERC20 {
        using Address for address;
        function safeTransfer(
            IERC20 token,
            address to,
            uint256 value
        ) internal {
            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
        }
        function safeTransferFrom(
            IERC20 token,
            address from,
            address to,
            uint256 value
        ) internal {
            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
        }
        /**
         * @dev Deprecated. This function has issues similar to the ones found in
         * {IERC20-approve}, and its usage is discouraged.
         *
         * Whenever possible, use {safeIncreaseAllowance} and
         * {safeDecreaseAllowance} instead.
         */
        function safeApprove(
            IERC20 token,
            address spender,
            uint256 value
        ) internal {
            // safeApprove should only be called when setting an initial allowance,
            // or when resetting it to zero. To increase and decrease it, use
            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
            require(
                (value == 0) || (token.allowance(address(this), spender) == 0),
                "SafeERC20: approve from non-zero to non-zero allowance"
            );
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
        }
        function safeIncreaseAllowance(
            IERC20 token,
            address spender,
            uint256 value
        ) internal {
            uint256 newAllowance = token.allowance(address(this), spender) + value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
        function safeDecreaseAllowance(
            IERC20 token,
            address spender,
            uint256 value
        ) internal {
            unchecked {
                uint256 oldAllowance = token.allowance(address(this), spender);
                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                uint256 newAllowance = oldAllowance - value;
                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
            }
        }
        /**
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
         * on the return value: the return value is optional (but if data is returned, it must not be false).
         * @param token The token targeted by the call.
         * @param data The call data (encoded using abi.encode or one of its variants).
         */
        function _callOptionalReturn(IERC20 token, bytes memory data) private {
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
            // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
            // the target address contains contract code and also asserts for success in the low-level call.
            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
            if (returndata.length > 0) {
                // Return data is optional
                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
            _;
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions anymore. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby removing any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Contract module that helps prevent reentrant calls to a function.
     *
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
     * available, which can be applied to functions to make sure there are no nested
     * (reentrant) calls to them.
     *
     * Note that because there is a single `nonReentrant` guard, functions marked as
     * `nonReentrant` may not call one another. This can be worked around by making
     * those functions `private`, and then adding `external` `nonReentrant` entry
     * points to them.
     *
     * TIP: If you would like to learn more about reentrancy and alternative ways
     * to protect against it, check out our blog post
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
     */
    abstract contract ReentrancyGuard {
        // Booleans are more expensive than uint256 or any type that takes up a full
        // word because each write operation emits an extra SLOAD to first read the
        // slot's contents, replace the bits taken up by the boolean, and then write
        // back. This is the compiler's defense against contract upgrades and
        // pointer aliasing, and it cannot be disabled.
        // The values being non-zero value makes deployment a bit more expensive,
        // but in exchange the refund on every call to nonReentrant will be lower in
        // amount. Since refunds are capped to a percentage of the total
        // transaction's gas, it is best to keep them low in cases like this one, to
        // increase the likelihood of the full refund coming into effect.
        uint256 private constant _NOT_ENTERED = 1;
        uint256 private constant _ENTERED = 2;
        uint256 private _status;
        constructor() {
            _status = _NOT_ENTERED;
        }
        /**
         * @dev Prevents a contract from calling itself, directly or indirectly.
         * Calling a `nonReentrant` function from another `nonReentrant`
         * function is not supported. It is possible to prevent this from happening
         * by making the `nonReentrant` function external, and making it call a
         * `private` function that does the actual work.
         */
        modifier nonReentrant() {
            // On the first call to nonReentrant, _notEntered will be true
            require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
            // Any calls to nonReentrant after this point will fail
            _status = _ENTERED;
            _;
            // By storing the original value once again, a refund is triggered (see
            // https://eips.ethereum.org/EIPS/eip-2200)
            _status = _NOT_ENTERED;
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import "./IUniRouter01.sol";
    interface IUniRouter02 is IUniRouter01 {
        function removeLiquidityETHSupportingFeeOnTransferTokens(
            address token,
            uint256 liquidity,
            uint256 amountTokenMin,
            uint256 amountETHMin,
            address to,
            uint256 deadline
        ) external returns (uint256 amountETH);
        function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
            address token,
            uint256 liquidity,
            uint256 amountTokenMin,
            uint256 amountETHMin,
            address to,
            uint256 deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint256 amountETH);
        function swapExactTokensForTokensSupportingFeeOnTransferTokens(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external;
        function swapExactETHForTokensSupportingFeeOnTransferTokens(
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external payable;
        function swapExactTokensForETHSupportingFeeOnTransferTokens(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external;
    }// SPDX-License-Identifier: MIT
    pragma solidity >=0.5.0;
    interface IWETH {
        function deposit() external payable;
        function transfer(address to, uint value) external returns (bool);
        function withdraw(uint) external;
    }// SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) external returns (bool);
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(
            address sender,
            address recipient,
            uint256 amount
        ) external returns (bool);
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize, which returns 0 for contracts in
            // construction, since the code is only stored at the end of the
            // constructor execution.
            uint256 size;
            assembly {
                size := extcodesize(account)
            }
            return size > 0;
        }
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCall(target, data, "Address: low-level call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            require(isContract(target), "Address: call to non-contract");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            require(isContract(target), "Address: static call to non-contract");
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(isContract(target), "Address: delegate call to non-contract");
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                // Look for revert reason and bubble it up if present
                if (returndata.length > 0) {
                    // The easiest way to bubble the revert reason is using memory via assembly
                    assembly {
                        let returndata_size := mload(returndata)
                        revert(add(32, returndata), returndata_size)
                    }
                } else {
                    revert(errorMessage);
                }
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    interface IUniRouter01 {
        function factory() external pure returns (address);
        function WETH() external pure returns (address);
        function addLiquidity(
            address tokenA,
            address tokenB,
            uint256 amountADesired,
            uint256 amountBDesired,
            uint256 amountAMin,
            uint256 amountBMin,
            address to,
            uint256 deadline
        )
            external
            returns (
                uint256 amountA,
                uint256 amountB,
                uint256 liquidity
            );
        function addLiquidityETH(
            address token,
            uint256 amountTokenDesired,
            uint256 amountTokenMin,
            uint256 amountETHMin,
            address to,
            uint256 deadline
        )
            external
            payable
            returns (
                uint256 amountToken,
                uint256 amountETH,
                uint256 liquidity
            );
        function removeLiquidity(
            address tokenA,
            address tokenB,
            uint256 liquidity,
            uint256 amountAMin,
            uint256 amountBMin,
            address to,
            uint256 deadline
        ) external returns (uint256 amountA, uint256 amountB);
        function removeLiquidityETH(
            address token,
            uint256 liquidity,
            uint256 amountTokenMin,
            uint256 amountETHMin,
            address to,
            uint256 deadline
        ) external returns (uint256 amountToken, uint256 amountETH);
        function removeLiquidityWithPermit(
            address tokenA,
            address tokenB,
            uint256 liquidity,
            uint256 amountAMin,
            uint256 amountBMin,
            address to,
            uint256 deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint256 amountA, uint256 amountB);
        function removeLiquidityETHWithPermit(
            address token,
            uint256 liquidity,
            uint256 amountTokenMin,
            uint256 amountETHMin,
            address to,
            uint256 deadline,
            bool approveMax,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external returns (uint256 amountToken, uint256 amountETH);
        function swapExactTokensForTokens(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external returns (uint256[] memory amounts);
        function swapTokensForExactTokens(
            uint256 amountOut,
            uint256 amountInMax,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external returns (uint256[] memory amounts);
        function swapExactETHForTokens(
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external payable returns (uint256[] memory amounts);
        function swapTokensForExactETH(
            uint256 amountOut,
            uint256 amountInMax,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external returns (uint256[] memory amounts);
        function swapExactTokensForETH(
            uint256 amountIn,
            uint256 amountOutMin,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external returns (uint256[] memory amounts);
        function swapETHForExactTokens(
            uint256 amountOut,
            address[] calldata path,
            address to,
            uint256 deadline
        ) external payable returns (uint256[] memory amounts);
        function quote(
            uint256 amountA,
            uint256 reserveA,
            uint256 reserveB
        ) external pure returns (uint256 amountB);
        function getAmountOut(
            uint256 amountIn,
            uint256 reserveIn,
            uint256 reserveOut
        ) external pure returns (uint256 amountOut);
        function getAmountIn(
            uint256 amountOut,
            uint256 reserveIn,
            uint256 reserveOut
        ) external pure returns (uint256 amountIn);
        function getAmountsOut(uint256 amountIn, address[] calldata path)
            external
            view
            returns (uint256[] memory amounts);
        function getAmountsIn(uint256 amountOut, address[] calldata path)
            external
            view
            returns (uint256[] memory amounts);
    }

    File 2 of 2: GroveToken
    // SPDX-License-Identifier: MIT
    
    pragma solidity 0.8.15;
    
    interface IERC20 {
    	function totalSupply() external view returns (uint256);
    
    	function balanceOf(address account) external view returns (uint256);
    
    	function transfer(address recipient, uint256 amount)
    	external
    	returns (bool);
    
    	function allowance(address owner, address spender)
    	external
    	view
    	returns (uint256);
    
    	function approve(address spender, uint256 amount) external returns (bool);
    
    	function transferFrom(
    		address sender,
    		address recipient,
    		uint256 amount
    	) external returns (bool);
    
    	event Transfer(address indexed from, address indexed to, uint256 value);
    
    	event Approval(
    		address indexed owner,
    		address indexed spender,
    		uint256 value
    	);
    }
    
    interface IFactory {
    	function createPair(address tokenA, address tokenB)
    	external
    	returns (address pair);
    
    	function getPair(address tokenA, address tokenB)
    	external
    	view
    	returns (address pair);
    }
    
    interface IRouter {
    	function factory() external pure returns (address);
    
    	function WETH() external pure returns (address);
    
    	function addLiquidityETH(
    		address token,
    		uint256 amountTokenDesired,
    		uint256 amountTokenMin,
    		uint256 amountETHMin,
    		address to,
    		uint256 deadline
    	)
    	external
    	payable
    	returns (
    		uint256 amountToken,
    		uint256 amountETH,
    		uint256 liquidity
    	);
    
    	function swapExactETHForTokensSupportingFeeOnTransferTokens(
    		uint256 amountOutMin,
    		address[] calldata path,
    		address to,
    		uint256 deadline
    	) external payable;
    
    	function swapExactTokensForETHSupportingFeeOnTransferTokens(
    		uint256 amountIn,
    		uint256 amountOutMin,
    		address[] calldata path,
    		address to,
    		uint256 deadline
    	) external;
    }
    
    interface IERC20Metadata is IERC20 {
    	function name() external view returns (string memory);
    	function symbol() external view returns (string memory);
    	function decimals() external view returns (uint8);
    }
    
    interface DividendPayingTokenInterface {
    	function dividendOf(address _owner) external view returns(uint256);
    	function withdrawDividend() external;
    	event DividendsDistributed(
    		address indexed from,
    		uint256 weiAmount
    	);
    	event DividendWithdrawn(
    		address indexed to,
    		uint256 weiAmount
    	);
    }
    
    interface DividendPayingTokenOptionalInterface {
    	function withdrawableDividendOf(address _owner) external view returns(uint256);
    	function withdrawnDividendOf(address _owner) external view returns(uint256);
    	function accumulativeDividendOf(address _owner) external view returns(uint256);
    }
    
    library SafeMath {
    
    	function add(uint256 a, uint256 b) internal pure returns (uint256) {
    		uint256 c = a + b;
    		require(c >= a, "SafeMath: addition overflow");
    
    		return c;
    	}
    
    	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    		return sub(a, b, "SafeMath: subtraction overflow");
    	}
    
    	function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    		require(b <= a, errorMessage);
    		uint256 c = a - b;
    
    		return c;
    	}
    
    	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    		// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    		// benefit is lost if 'b' is also tested.
    		// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
    		if (a == 0) {
    			return 0;
    		}
    
    		uint256 c = a * b;
    		require(c / a == b, "SafeMath: multiplication overflow");
    
    		return c;
    	}
    
    	function div(uint256 a, uint256 b) internal pure returns (uint256) {
    		return div(a, b, "SafeMath: division by zero");
    	}
    
    	function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    		require(b > 0, errorMessage);
    		uint256 c = a / b;
    		// assert(a == b * c + a % b); // There is no case in which this doesn't hold
    
    		return c;
    	}
    
    	function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    		return mod(a, b, "SafeMath: modulo by zero");
    	}
    
    	function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
    		require(b != 0, errorMessage);
    		return a % b;
    	}
    }
    
    library SafeMathInt {
    	int256 private constant MIN_INT256 = int256(1) << 255;
    	int256 private constant MAX_INT256 = ~(int256(1) << 255);
    
    	function mul(int256 a, int256 b) internal pure returns (int256) {
    		int256 c = a * b;
    
    		// Detect overflow when multiplying MIN_INT256 with -1
    		require(c != MIN_INT256 || (a & MIN_INT256) != (b & MIN_INT256));
    		require((b == 0) || (c / b == a));
    		return c;
    	}
    	function div(int256 a, int256 b) internal pure returns (int256) {
    		// Prevent overflow when dividing MIN_INT256 by -1
    		require(b != -1 || a != MIN_INT256);
    
    		// Solidity already throws when dividing by 0.
    		return a / b;
    	}
    	function sub(int256 a, int256 b) internal pure returns (int256) {
    		int256 c = a - b;
    		require((b >= 0 && c <= a) || (b < 0 && c > a));
    		return c;
    	}
    	function add(int256 a, int256 b) internal pure returns (int256) {
    		int256 c = a + b;
    		require((b >= 0 && c >= a) || (b < 0 && c < a));
    		return c;
    	}
    	function abs(int256 a) internal pure returns (int256) {
    		require(a != MIN_INT256);
    		return a < 0 ? -a : a;
    	}
    	function toUint256Safe(int256 a) internal pure returns (uint256) {
    		require(a >= 0);
    		return uint256(a);
    	}
    }
    
    library SafeMathUint {
    	function toInt256Safe(uint256 a) internal pure returns (int256) {
    		int256 b = int256(a);
    		require(b >= 0);
    		return b;
    	}
    }
    
    library IterableMapping {
    	struct Map {
    		address[] keys;
    		mapping(address => uint) values;
    		mapping(address => uint) indexOf;
    		mapping(address => bool) inserted;
    	}
    
    	function get(Map storage map, address key) public view returns (uint) {
    		return map.values[key];
    	}
    
    	function getIndexOfKey(Map storage map, address key) public view returns (int) {
    		if(!map.inserted[key]) {
    			return -1;
    		}
    		return int(map.indexOf[key]);
    	}
    
    	function getKeyAtIndex(Map storage map, uint index) public view returns (address) {
    		return map.keys[index];
    	}
    
    	function size(Map storage map) public view returns (uint) {
    		return map.keys.length;
    	}
    
    	function set(Map storage map, address key, uint val) public {
    		if (map.inserted[key]) {
    			map.values[key] = val;
    		} else {
    			map.inserted[key] = true;
    			map.values[key] = val;
    			map.indexOf[key] = map.keys.length;
    			map.keys.push(key);
    		}
    	}
    
    	function remove(Map storage map, address key) public {
    		if (!map.inserted[key]) {
    			return;
    		}
    
    		delete map.inserted[key];
    		delete map.values[key];
    
    		uint index = map.indexOf[key];
    		uint lastIndex = map.keys.length - 1;
    		address lastKey = map.keys[lastIndex];
    
    		map.indexOf[lastKey] = index;
    		delete map.indexOf[key];
    
    		map.keys[index] = lastKey;
    		map.keys.pop();
    	}
    }
    
    abstract contract Context {
    	function _msgSender() internal view virtual returns (address) {
    		return msg.sender;
    	}
    
    	function _msgData() internal view virtual returns (bytes calldata) {
    		this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
    		return msg.data;
    	}
    }
    
    contract Ownable is Context {
    	address private _owner;
    
    	event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    	constructor () {
    		address msgSender = _msgSender();
    		_owner = msgSender;
    		emit OwnershipTransferred(address(0), msgSender);
    	}
    
    	function owner() public view returns (address) {
    		return _owner;
    	}
    
    	modifier onlyOwner() {
    		require(_owner == _msgSender(), "Ownable: caller is not the owner");
    		_;
    	}
    
    	function renounceOwnership() public virtual onlyOwner {
    		emit OwnershipTransferred(_owner, address(0));
    		_owner = address(0);
    	}
    
    	function transferOwnership(address newOwner) public virtual onlyOwner {
    		require(newOwner != address(0), "Ownable: new owner is the zero address");
    		emit OwnershipTransferred(_owner, newOwner);
    		_owner = newOwner;
    	}
    }
    
    contract ERC20 is Context, IERC20, IERC20Metadata {
    	using SafeMath for uint256;
    
    	mapping(address => uint256) private _balances;
    	mapping(address => mapping(address => uint256)) private _allowances;
    
    	uint256 private _totalSupply;
    	string private _name;
    	string private _symbol;
    
    	constructor(string memory name_, string memory symbol_) {
    		_name = name_;
    		_symbol = symbol_;
    	}
    
    	function name() public view virtual override returns (string memory) {
    		return _name;
    	}
    
    	function symbol() public view virtual override returns (string memory) {
    		return _symbol;
    	}
    
    	function decimals() public view virtual override returns (uint8) {
    		return 18;
    	}
    
    	function totalSupply() public view virtual override returns (uint256) {
    		return _totalSupply;
    	}
    
    	function balanceOf(address account) public view virtual override returns (uint256) {
    		return _balances[account];
    	}
    
    	function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    		_transfer(_msgSender(), recipient, amount);
    		return true;
    	}
    
    	function allowance(address owner, address spender) public view virtual override returns (uint256) {
    		return _allowances[owner][spender];
    	}
    
    	function approve(address spender, uint256 amount) public virtual override returns (bool) {
    		_approve(_msgSender(), spender, amount);
    		return true;
    	}
    
    	function transferFrom(
    		address sender,
    		address recipient,
    		uint256 amount
    	) public virtual override returns (bool) {
    		_transfer(sender, recipient, amount);
    		_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
    		return true;
    	}
    
    	function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
    		_approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
    		return true;
    	}
    
    	function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
    		_approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
    		return true;
    	}
    
    	function _transfer(
    		address sender,
    		address recipient,
    		uint256 amount
    	) internal virtual {
    		require(sender != address(0), "ERC20: transfer from the zero address");
    		require(recipient != address(0), "ERC20: transfer to the zero address");
    		_beforeTokenTransfer(sender, recipient, amount);
    		_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
    		_balances[recipient] = _balances[recipient].add(amount);
    		emit Transfer(sender, recipient, amount);
    	}
    
    	function _mint(address account, uint256 amount) internal virtual {
    		require(account != address(0), "ERC20: mint to the zero address");
    		_beforeTokenTransfer(address(0), account, amount);
    		_totalSupply = _totalSupply.add(amount);
    		_balances[account] = _balances[account].add(amount);
    		emit Transfer(address(0), account, amount);
    	}
    
    	function _burn(address account, uint256 amount) internal virtual {
    		require(account != address(0), "ERC20: burn from the zero address");
    		_beforeTokenTransfer(account, address(0), amount);
    		_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
    		_totalSupply = _totalSupply.sub(amount);
    		emit Transfer(account, address(0), amount);
    	}
    
    	function _approve(
    		address owner,
    		address spender,
    		uint256 amount
    	) internal virtual {
    		require(owner != address(0), "ERC20: approve from the zero address");
    		require(spender != address(0), "ERC20: approve to the zero address");
    		_allowances[owner][spender] = amount;
    		emit Approval(owner, spender, amount);
    	}
    
    	function _beforeTokenTransfer(
    		address from,
    		address to,
    		uint256 amount
    	) internal virtual {}
    }
    
    contract DividendPayingToken is ERC20, Ownable, DividendPayingTokenInterface, DividendPayingTokenOptionalInterface {
    	using SafeMath for uint256;
    	using SafeMathUint for uint256;
    	using SafeMathInt for int256;
    
    	uint256 constant internal magnitude = 2**128;
    	uint256 internal magnifiedDividendPerShare;
    	uint256 public totalDividendsDistributed;
    	address public rewardToken;
    	IRouter public uniswapV2Router;
    
    	mapping(address => int256) internal magnifiedDividendCorrections;
    	mapping(address => uint256) internal withdrawnDividends;
    
    	constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {}
    
    	receive() external payable {}
    
    	function distributeDividendsUsingAmount(uint256 amount) public onlyOwner {
    		require(totalSupply() > 0);
    		if (amount > 0) {
    			magnifiedDividendPerShare = magnifiedDividendPerShare.add((amount).mul(magnitude) / totalSupply());
    			emit DividendsDistributed(msg.sender, amount);
    			totalDividendsDistributed = totalDividendsDistributed.add(amount);
    		}
    	}
    	function withdrawDividend() public virtual override onlyOwner {
    		_withdrawDividendOfUser(payable(msg.sender));
    	}
    	function _withdrawDividendOfUser(address payable user) internal returns (uint256) {
    		uint256 _withdrawableDividend = withdrawableDividendOf(user);
    		if (_withdrawableDividend > 0) {
    			withdrawnDividends[user] = withdrawnDividends[user].add(_withdrawableDividend);
    			emit DividendWithdrawn(user, _withdrawableDividend);
    			(bool success) = IERC20(rewardToken).transfer(user, _withdrawableDividend);
    			if(!success) {
    				withdrawnDividends[user] = withdrawnDividends[user].sub(_withdrawableDividend);
    				return 0;
    			}
    			return _withdrawableDividend;
    		}
    		return 0;
    	}
    	function dividendOf(address _owner) public view override returns(uint256) {
    		return withdrawableDividendOf(_owner);
    	}
    	function withdrawableDividendOf(address _owner) public view override returns(uint256) {
    		return accumulativeDividendOf(_owner).sub(withdrawnDividends[_owner]);
    	}
    	function withdrawnDividendOf(address _owner) public view override returns(uint256) {
    		return withdrawnDividends[_owner];
    	}
    	function accumulativeDividendOf(address _owner) public view override returns(uint256) {
    		return magnifiedDividendPerShare.mul(balanceOf(_owner)).toInt256Safe()
    		.add(magnifiedDividendCorrections[_owner]).toUint256Safe() / magnitude;
    	}
    	function _transfer(address from, address to, uint256 value) internal virtual override {
    		require(false);
    		int256 _magCorrection = magnifiedDividendPerShare.mul(value).toInt256Safe();
    		magnifiedDividendCorrections[from] = magnifiedDividendCorrections[from].add(_magCorrection);
    		magnifiedDividendCorrections[to] = magnifiedDividendCorrections[to].sub(_magCorrection);
    	}
    	function _mint(address account, uint256 value) internal override {
    		super._mint(account, value);
    		magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account]
    		.sub( (magnifiedDividendPerShare.mul(value)).toInt256Safe() );
    	}
    	function _burn(address account, uint256 value) internal override {
    		super._burn(account, value);
    		magnifiedDividendCorrections[account] = magnifiedDividendCorrections[account]
    		.add( (magnifiedDividendPerShare.mul(value)).toInt256Safe() );
    	}
    	function _setBalance(address account, uint256 newBalance) internal {
    		uint256 currentBalance = balanceOf(account);
    		if(newBalance > currentBalance) {
    			uint256 mintAmount = newBalance.sub(currentBalance);
    			_mint(account, mintAmount);
    		} else if(newBalance < currentBalance) {
    			uint256 burnAmount = currentBalance.sub(newBalance);
    			_burn(account, burnAmount);
    		}
    	}
    	function _setRewardToken(address token) internal onlyOwner {
    	    rewardToken = token;
    	}
    	function _setUniswapRouter(address router) internal onlyOwner {
    	    uniswapV2Router = IRouter(router);
    	}
    }
    
    contract GroveToken is Ownable, ERC20 {
    
    	IRouter public uniswapV2Router;
    	address public immutable uniswapV2Pair;
    
    	string private constant _name =  "Grove Token";
    	string private constant _symbol = "GVR";
    	uint8 private constant _decimals = 18;
    
    	GroveTokenDividendTracker public dividendTracker;
    
    	bool public isTradingEnabled;
    
    	// initialSupply
    	uint256 constant initialSupply = 5000000000000000 * (10**18);
    
    	uint256 public maxWalletAmount = initialSupply * 3;
    
    	uint256 public maxTxAmount = initialSupply * 3;
    
    	bool private _swapping;
    	uint256 public minimumTokensBeforeSwap = 150000000 * (10**18);
    
    	address public liquidityWallet;
    	address public marketingWallet;
    	address public buyBackWallet;
    	address private bridge;
    
    	struct CustomTaxPeriod {
    		bytes23 periodName;
    		uint8 blocksInPeriod;
    		uint256 timeInPeriod;
    		uint8 liquidityFeeOnBuy;
    		uint8 liquidityFeeOnSell;
    		uint8 marketingFeeOnBuy;
    		uint8 marketingFeeOnSell;
    		uint8 buyBackFeeOnBuy;
    		uint8 buyBackFeeOnSell;
            uint8 burnFeeOnBuy;
    		uint8 burnFeeOnSell;
    		uint8 holdersFeeOnBuy;
    		uint8 holdersFeeOnSell;
    	}
    
    	// Base taxes
    	CustomTaxPeriod private _base = CustomTaxPeriod("base",0,0,1,1,3,3,1,1,2,2,3,3);
    
    	mapping (address => bool) private _isAllowedToTradeWhenDisabled;
    	mapping (address => bool) private _isExcludedFromFee;
    	mapping (address => bool) private _isExcludedFromMaxTransactionLimit;
    	mapping (address => bool) private _isExcludedFromMaxWalletLimit;
    	mapping (address => bool) public automatedMarketMakerPairs;
    
    	uint8 private _liquidityFee;
    	uint8 private _marketingFee;
    	uint8 private _buyBackFee;
        uint8 private _burnFee;
    	uint8 private _holdersFee;
    	uint8 private _totalFee;
    
    	event AutomatedMarketMakerPairChange(address indexed pair, bool indexed value);
    	event UniswapV2RouterChange(address indexed newAddress, address indexed oldAddress);
    	event WalletChange(string indexed indentifier, address indexed newWallet, address indexed oldWallet);
    	event FeeChange(string indexed identifier, uint8 liquidityFee, uint8 marketingFee, uint8 buyBackFee, uint8 burnFee, uint8 holdersFee);
    	event CustomTaxPeriodChange(uint256 indexed newValue, uint256 indexed oldValue, string indexed taxType, bytes23 period);
    	event MaxTransactionAmountChange(uint256 indexed newValue, uint256 indexed oldValue);
    	event MaxWalletAmountChange(uint256 indexed newValue, uint256 indexed oldValue);
    	event ExcludeFromFeesChange(address indexed account, bool isExcluded);
    	event ExcludeFromMaxTransferChange(address indexed account, bool isExcluded);
    	event ExcludeFromMaxWalletChange(address indexed account, bool isExcluded);
    	event AllowedWhenTradingDisabledChange(address indexed account, bool isExcluded);
    	event MinTokenAmountBeforeSwapChange(uint256 indexed newValue, uint256 indexed oldValue);
    	event MinTokenAmountForDividendsChange(uint256 indexed newValue, uint256 indexed oldValue);
    	event DividendsSent(uint256 tokensSwapped);
    	event SwapAndLiquify(uint256 tokensSwapped, uint256 ethReceived,uint256 tokensIntoLiqudity);
        event ClaimETHOverflow(uint256 amount);
        event TokenBurn(uint8 _burnFee, uint256 burnAmount);
    	event FeesApplied(uint8 liquidityFee, uint8 marketingFee, uint8 buyBackFee, uint8 burnFee, uint8 holdersFee, uint8 totalFee);
    	event BridgeContractChange(address bridgeContract);
    
    	modifier hasMintPermission {
    		require(msg.sender == bridge, "only bridge contract can mint");
    		_;
    	}
    
    	constructor() ERC20(_name, _symbol) {
    		dividendTracker = new GroveTokenDividendTracker();
    		dividendTracker.setUniswapRouter(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
            dividendTracker.setRewardToken(address(this));
    
    		liquidityWallet = owner();
    		marketingWallet = owner();
    		buyBackWallet = owner();
    
    		IRouter _uniswapV2Router = IRouter(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    		address _uniswapV2Pair = IFactory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
    		uniswapV2Router = _uniswapV2Router;
    		uniswapV2Pair = _uniswapV2Pair;
    		_setAutomatedMarketMakerPair(_uniswapV2Pair, true);
    
    		_isExcludedFromFee[owner()] = true;
    		_isExcludedFromFee[address(this)] = true;
    		_isExcludedFromFee[address(dividendTracker)] = true;
    
    		dividendTracker.excludeFromDividends(address(dividendTracker));
    		dividendTracker.excludeFromDividends(address(this));
    		dividendTracker.excludeFromDividends(address(0x000000000000000000000000000000000000dEaD));
    		dividendTracker.excludeFromDividends(owner());
    		dividendTracker.excludeFromDividends(address(_uniswapV2Router));
    
    		_isAllowedToTradeWhenDisabled[owner()] = true;
    
    		_isExcludedFromMaxTransactionLimit[address(dividendTracker)] = true;
    		_isExcludedFromMaxTransactionLimit[address(this)] = true;
    
    		_isExcludedFromMaxWalletLimit[_uniswapV2Pair] = true;
    		_isExcludedFromMaxWalletLimit[address(dividendTracker)] = true;
    		_isExcludedFromMaxWalletLimit[address(uniswapV2Router)] = true;
    		_isExcludedFromMaxWalletLimit[address(this)] = true;
    		_isExcludedFromMaxWalletLimit[owner()] = true;
    
    		_mint(owner(), initialSupply);
    	}
    
    	receive() external payable {}
    
    	// Setters
    	function activateTrading() external onlyOwner {
    		isTradingEnabled = true;
    	}
    	function deactivateTrading() external onlyOwner {
    		isTradingEnabled = false;
    	}
    	function _setAutomatedMarketMakerPair(address pair, bool value) private {
    		require(automatedMarketMakerPairs[pair] != value, "GroveToken: Automated market maker pair is already set to that value");
    		automatedMarketMakerPairs[pair] = value;
    		if(value) {
    			dividendTracker.excludeFromDividends(pair);
    		}
    		emit AutomatedMarketMakerPairChange(pair, value);
    	}
    	function allowTradingWhenDisabled(address account, bool allowed) external onlyOwner {
    		_isAllowedToTradeWhenDisabled[account] = allowed;
    		emit AllowedWhenTradingDisabledChange(account, allowed);
    	}
    	function excludeFromFees(address account, bool excluded) external onlyOwner {
    		require(_isExcludedFromFee[account] != excluded, "GroveToken: Account is already the value of 'excluded'");
    		_isExcludedFromFee[account] = excluded;
    		emit ExcludeFromFeesChange(account, excluded);
    	}
    	function excludeFromDividends(address account) external onlyOwner {
    		dividendTracker.excludeFromDividends(account);
    	}
    	function excludeFromMaxTransactionLimit(address account, bool excluded) external onlyOwner {
    		require(_isExcludedFromMaxTransactionLimit[account] != excluded, "GroveToken: Account is already the value of 'excluded'");
    		_isExcludedFromMaxTransactionLimit[account] = excluded;
    		emit ExcludeFromMaxTransferChange(account, excluded);
    	}
    	function excludeFromMaxWalletLimit(address account, bool excluded) external onlyOwner {
    		require(_isExcludedFromMaxWalletLimit[account] != excluded, "GroveToken: Account is already the value of 'excluded'");
    		_isExcludedFromMaxWalletLimit[account] = excluded;
    		emit ExcludeFromMaxWalletChange(account, excluded);
    	}
    	function setWallets(address newLiquidityWallet, address newMarketingWallet, address newBuyBackWallet) external onlyOwner {
    		if(liquidityWallet != newLiquidityWallet) {
    			require(newLiquidityWallet != address(0), "GroveToken: The liquidityWallet cannot be 0");
    			emit WalletChange("liquidityWallet", newLiquidityWallet, liquidityWallet);
    			liquidityWallet = newLiquidityWallet;
    		}
    		if(marketingWallet != newMarketingWallet) {
    			require(newMarketingWallet != address(0), "GroveToken: The marketingWallet cannot be 0");
    			emit WalletChange("marketingWallet", newMarketingWallet, marketingWallet);
    			marketingWallet = newMarketingWallet;
    		}
    		if(buyBackWallet != newBuyBackWallet) {
    			require(newBuyBackWallet != address(0), "GroveToken: The buyBackWallet cannot be 0");
    			emit WalletChange("buyBackWallet", newBuyBackWallet, buyBackWallet);
    			buyBackWallet = newBuyBackWallet;
    		}
    	}
    	// Base fees
    	function setBaseFeesOnBuy(uint8 _liquidityFeeOnBuy, uint8 _marketingFeeOnBuy, uint8 _buyBackFeeOnBuy, uint8 _burnFeeOnBuy, uint8 _holdersFeeOnBuy) external onlyOwner {
    		_setCustomBuyTaxPeriod(_base, _liquidityFeeOnBuy, _marketingFeeOnBuy, _buyBackFeeOnBuy, _burnFeeOnBuy, _holdersFeeOnBuy);
    		emit FeeChange("baseFees-Buy", _liquidityFeeOnBuy, _marketingFeeOnBuy, _buyBackFeeOnBuy, _burnFeeOnBuy, _holdersFeeOnBuy);
    	}
    	function setBaseFeesOnSell(uint8 _liquidityFeeOnSell, uint8 _marketingFeeOnSell, uint8 _buyBackFeeOnSell, uint8 _burnFeeOnSell, uint8 _holdersFeeOnSell) external onlyOwner {
    		_setCustomSellTaxPeriod(_base, _liquidityFeeOnSell, _marketingFeeOnSell, _buyBackFeeOnSell, _burnFeeOnSell, _holdersFeeOnSell);
    		emit FeeChange("baseFees-Sell", _liquidityFeeOnSell, _marketingFeeOnSell, _buyBackFeeOnSell, _burnFeeOnSell, _holdersFeeOnSell);
    	}
    	function setUniswapRouter(address newAddress) external onlyOwner {
    		require(newAddress != address(uniswapV2Router), "GroveToken: The router already has that address");
    		emit UniswapV2RouterChange(newAddress, address(uniswapV2Router));
    		uniswapV2Router = IRouter(newAddress);
    		dividendTracker.setUniswapRouter(newAddress);
    	}
    	function setMaxTransactionAmount(uint256 newValue) external onlyOwner {
    		require(newValue != maxTxAmount, "GroveToken: Cannot update maxTxAmount to same value");
    		emit MaxTransactionAmountChange(newValue, maxTxAmount);
    		maxTxAmount = newValue;
    	}
    	function setMaxWalletAmount(uint256 newValue) external onlyOwner {
    		require(newValue != maxWalletAmount, "GroveToken: Cannot update maxWalletAmount to same value");
    		emit MaxWalletAmountChange(newValue, maxWalletAmount);
    		maxWalletAmount = newValue;
    	}
    	function setMinimumTokensBeforeSwap(uint256 newValue) external onlyOwner {
    		require(newValue != minimumTokensBeforeSwap, "GroveToken: Cannot update minimumTokensBeforeSwap to same value");
    		emit MinTokenAmountBeforeSwapChange(newValue, minimumTokensBeforeSwap);
    		minimumTokensBeforeSwap = newValue;
    	}
    	function setMinimumTokenBalanceForDividends(uint256 newValue) external onlyOwner {
    		dividendTracker.setTokenBalanceForDividends(newValue);
    	}
    	function claim() external {
    		dividendTracker.processAccount(payable(msg.sender), false);
        }
    	function claimETHOverflow(uint256 amount) external onlyOwner {
    	    require(amount < address(this).balance, "GroveToken: Cannot send more than contract balance");
            (bool success,) = address(owner()).call{value : amount}("");
            if (success){
                emit ClaimETHOverflow(amount);
            }
    	}
    	function mint(address account, uint256 value) external hasMintPermission returns(bool) {
    		_mint(account, value);
    		return true;
    	}
    	function burn(uint256 value) external {
    		_burn(msg.sender, value);
    	}
    
        function bridgeContract() external view returns (address) {
            return bridge;
        }
    
        function setBridgeContract(address _bridgeContract) external onlyOwner {
            require(_bridgeContract != address(0x0) && _bridgeContract != bridge, "invalid address");
            bridge = _bridgeContract;
    		emit BridgeContractChange(_bridgeContract);
        }
    
    	// Getters
    	function getTotalDividendsDistributed() external view returns (uint256) {
    		return dividendTracker.totalDividendsDistributed();
    	}
    	function withdrawableDividendOf(address account) external view returns(uint256) {
    		return dividendTracker.withdrawableDividendOf(account);
    	}
    	function dividendTokenBalanceOf(address account) external view returns (uint256) {
    		return dividendTracker.balanceOf(account);
    	}
    	function getNumberOfDividendTokenHolders() external view returns(uint256) {
    		return dividendTracker.getNumberOfTokenHolders();
    	}
    	function getBaseBuyFees() external view returns (uint8, uint8, uint8, uint8, uint8){
    		return (_base.liquidityFeeOnBuy, _base.marketingFeeOnBuy, _base.buyBackFeeOnBuy, _base.burnFeeOnBuy, _base.holdersFeeOnBuy);
    	}
    	function getBaseSellFees() external view returns (uint8, uint8, uint8, uint8, uint8){
    		return (_base.liquidityFeeOnSell, _base.marketingFeeOnSell, _base.buyBackFeeOnSell, _base.burnFeeOnSell, _base.holdersFeeOnSell);
    	}
    
    	// Main
    	function _transfer(
    		address from,
    		address to,
    		uint256 amount
    		) internal override {
    			require(from != address(0), "ERC20: transfer from the zero address");
    			require(to != address(0), "ERC20: transfer to the zero address");
    
    			if(amount == 0) {
    				super._transfer(from, to, 0);
    				return;
    			}
    
    			bool isBuyFromLp = automatedMarketMakerPairs[from];
    			bool isSelltoLp = automatedMarketMakerPairs[to];
    
    			if(!_isAllowedToTradeWhenDisabled[from] && !_isAllowedToTradeWhenDisabled[to]) {
    				require(isTradingEnabled, "GroveToken: Trading is currently disabled.");
    				if (!_isExcludedFromMaxTransactionLimit[to] && !_isExcludedFromMaxTransactionLimit[from]) {
    					require(amount <= maxTxAmount, "GroveToken: Buy amount exceeds the maxTxBuyAmount.");
    				}
    				if (!_isExcludedFromMaxWalletLimit[to]) {
    					require((balanceOf(to) + amount) <= maxWalletAmount, "GroveToken: Expected wallet amount exceeds the maxWalletAmount.");
    				}
    			}
    
    			_adjustTaxes(isBuyFromLp, isSelltoLp);
    			bool canSwap = balanceOf(address(this)) >= minimumTokensBeforeSwap;
    
    			if (
    				isTradingEnabled &&
    				canSwap &&
    				!_swapping &&
    				_totalFee > 0 &&
    				automatedMarketMakerPairs[to]
    			) {
    				_swapping = true;
    				_swapAndLiquify();
    				_swapping = false;
    			}
    
    			bool takeFee = !_swapping && isTradingEnabled;
    
    			if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){
    				takeFee = false;
    			}
    			if (takeFee && _totalFee > 0) {
    				uint256 fee = amount * _totalFee / 100;
                    uint256 burnAmount = amount * _burnFee / 100;
    				amount = amount - fee;
    				super._transfer(from, address(this), fee);
    
                    if (burnAmount > 0) {
                        super._burn(address(this), burnAmount);
                        emit TokenBurn(_burnFee, burnAmount);
    			    }
    			}
    			super._transfer(from, to, amount);
    
                try dividendTracker.setBalance(payable(from), balanceOf(from)) {} catch {}
    		    try dividendTracker.setBalance(payable(to), balanceOf(to)) {} catch {}
    	}
    	function _adjustTaxes(bool isBuyFromLp, bool isSelltoLp) private {
    		_liquidityFee = 0;
    		_marketingFee = 0;
    		_buyBackFee = 0;
            _burnFee = 0;
    		_holdersFee = 0;
    
    		if (isBuyFromLp) {
    			_liquidityFee = _base.liquidityFeeOnBuy;
    			_marketingFee = _base.marketingFeeOnBuy;
    			_buyBackFee = _base.buyBackFeeOnBuy;
                _burnFee = _base.burnFeeOnBuy;
    			_holdersFee = _base.holdersFeeOnBuy;
    		}
    	    if (isSelltoLp) {
    	    	_liquidityFee = _base.liquidityFeeOnSell;
    			_marketingFee = _base.marketingFeeOnSell;
    			_buyBackFee = _base.buyBackFeeOnSell;
                _burnFee = _base.burnFeeOnSell;
    			_holdersFee = _base.holdersFeeOnSell;
    		}
            if (!isSelltoLp && !isBuyFromLp) {
    			_liquidityFee = _base.liquidityFeeOnSell;
    			_marketingFee = _base.marketingFeeOnSell;
    			_buyBackFee = _base.buyBackFeeOnSell;
                _burnFee = _base.burnFeeOnSell;
    			_holdersFee = _base.holdersFeeOnSell;
    		}
    		_totalFee = _liquidityFee + _marketingFee + _buyBackFee + _burnFee + _holdersFee;
    		emit FeesApplied(_liquidityFee, _marketingFee, _buyBackFee, _burnFee, _holdersFee, _totalFee);
    	}
    	function _setCustomSellTaxPeriod(CustomTaxPeriod storage map,
    		uint8 _liquidityFeeOnSell,
    		uint8 _marketingFeeOnSell,
    		uint8 _buyBackFeeOnSell,
            uint8 _burnFeeOnSell,
    		uint8 _holdersFeeOnSell
    	) private {
    		if (map.liquidityFeeOnSell != _liquidityFeeOnSell) {
    			emit CustomTaxPeriodChange(_liquidityFeeOnSell, map.liquidityFeeOnSell, "liquidityFeeOnSell", map.periodName);
    			map.liquidityFeeOnSell = _liquidityFeeOnSell;
    		}
    		if (map.marketingFeeOnSell != _marketingFeeOnSell) {
    			emit CustomTaxPeriodChange(_marketingFeeOnSell, map.marketingFeeOnSell, "marketingFeeOnSell", map.periodName);
    			map.marketingFeeOnSell = _marketingFeeOnSell;
    		}
    		if (map.buyBackFeeOnSell != _buyBackFeeOnSell) {
    			emit CustomTaxPeriodChange(_buyBackFeeOnSell, map.buyBackFeeOnSell, "buyBackFeeOnSell", map.periodName);
    			map.buyBackFeeOnSell = _buyBackFeeOnSell;
    		}
            if (map.burnFeeOnSell != _burnFeeOnSell) {
    			emit CustomTaxPeriodChange(_burnFeeOnSell, map.burnFeeOnSell, "burnFeeOnSell", map.periodName);
    			map.burnFeeOnSell = _burnFeeOnSell;
    		}
    		if (map.holdersFeeOnSell != _holdersFeeOnSell) {
    			emit CustomTaxPeriodChange(_holdersFeeOnSell, map.holdersFeeOnSell, "holdersFeeOnSell", map.periodName);
    			map.holdersFeeOnSell = _holdersFeeOnSell;
    		}
    	}
    	function _setCustomBuyTaxPeriod(CustomTaxPeriod storage map,
    		uint8 _liquidityFeeOnBuy,
    		uint8 _marketingFeeOnBuy,
    		uint8 _buyBackFeeOnBuy,
            uint8 _burnFeeOnBuy,
    		uint8 _holdersFeeOnBuy
    		) private {
    		if (map.liquidityFeeOnBuy != _liquidityFeeOnBuy) {
    			emit CustomTaxPeriodChange(_liquidityFeeOnBuy, map.liquidityFeeOnBuy, "liquidityFeeOnBuy", map.periodName);
    			map.liquidityFeeOnBuy = _liquidityFeeOnBuy;
    		}
    		if (map.marketingFeeOnBuy != _marketingFeeOnBuy) {
    			emit CustomTaxPeriodChange(_marketingFeeOnBuy, map.marketingFeeOnBuy, "marketingFeeOnBuy", map.periodName);
    			map.marketingFeeOnBuy = _marketingFeeOnBuy;
    		}
    		if (map.buyBackFeeOnBuy != _buyBackFeeOnBuy) {
    			emit CustomTaxPeriodChange(_buyBackFeeOnBuy, map.buyBackFeeOnBuy, "buyBackFeeOnBuy", map.periodName);
    			map.buyBackFeeOnBuy = _buyBackFeeOnBuy;
    		}
            if (map.burnFeeOnBuy != _burnFeeOnBuy) {
    			emit CustomTaxPeriodChange(_burnFeeOnBuy, map.burnFeeOnBuy, "burnFeeOnBuy", map.periodName);
    			map.burnFeeOnBuy = _burnFeeOnBuy;
    		}
    		if (map.holdersFeeOnBuy != _holdersFeeOnBuy) {
    			emit CustomTaxPeriodChange(_holdersFeeOnBuy, map.holdersFeeOnBuy, "holdersFeeOnBuy", map.periodName);
    			map.holdersFeeOnBuy = _holdersFeeOnBuy;
    		}
    	}
    	function _swapAndLiquify() private {
    		uint256 contractBalance = balanceOf(address(this));
    		uint256 initialETHBalance = address(this).balance;
    
    		uint256 amountToLiquify = contractBalance * _liquidityFee / _totalFee / 2;
            uint256 amountForHolders = contractBalance * _holdersFee / _totalFee;
    		uint256 amountToSwap = contractBalance - (amountToLiquify + amountForHolders);
    
    		_swapTokensForETH(amountToSwap);
    
    		uint256 ETHBalanceAfterSwap = address(this).balance  - initialETHBalance;
    		uint256 totalETHFee = _totalFee - ((_liquidityFee / 2) + _burnFee + _holdersFee);
    		uint256 amountETHLiquidity = ETHBalanceAfterSwap * _liquidityFee / totalETHFee / 2;
            uint256 amountETHMarketing = ETHBalanceAfterSwap * _marketingFee / totalETHFee;
    		uint256 amountETHBuyBack = ETHBalanceAfterSwap - (amountETHLiquidity + amountETHMarketing);
    
    		payable(buyBackWallet).transfer(amountETHBuyBack);
            payable(marketingWallet).transfer(amountETHMarketing);
    
    		if (amountToLiquify > 0) {
    			_addLiquidity(amountToLiquify, amountETHLiquidity);
    			emit SwapAndLiquify(amountToSwap, amountETHLiquidity, amountToLiquify);
            }
    
    		(bool success) = IERC20(address(this)).transfer(address(dividendTracker), amountForHolders);
    		if(success) {
    			dividendTracker.distributeDividendsUsingAmount(amountForHolders);
    			emit DividendsSent(amountForHolders);
    		}
    	}
    	function _swapTokensForETH(uint256 tokenAmount) private {
    		address[] memory path = new address[](2);
    		path[0] = address(this);
    		path[1] = uniswapV2Router.WETH();
    		_approve(address(this), address(uniswapV2Router), tokenAmount);
    		uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
    			tokenAmount,
    			0, // accept any amount of ETH
    			path,
    			address(this),
    			block.timestamp
    		);
    	}
    	function _addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
    		_approve(address(this), address(uniswapV2Router), tokenAmount);
    		uniswapV2Router.addLiquidityETH{value: ethAmount}(
    			address(this),
    			tokenAmount,
    			0, // slippage is unavoidable
    			0, // slippage is unavoidable
    			liquidityWallet,
    			block.timestamp
    		);
    	}
    }
    
    contract GroveTokenDividendTracker is DividendPayingToken {
    	using SafeMath for uint256;
    	using SafeMathInt for int256;
    	using IterableMapping for IterableMapping.Map;
    
    	IterableMapping.Map private tokenHoldersMap;
    
    	mapping (address => bool) public excludedFromDividends;
    	mapping (address => uint256) public lastClaimTimes;
    	uint256 public claimWait;
    	uint256 public minimumTokenBalanceForDividends;
    
    	event ExcludeFromDividends(address indexed account);
    	event ClaimWaitUpdated(uint256 indexed newValue, uint256 indexed oldValue);
    	event Claim(address indexed account, uint256 amount, bool indexed automatic);
    
    	constructor() DividendPayingToken("GroveToken_Dividend_Tracker", "GroveToken_Dividend_Tracker") {
    		claimWait = 3600;
    		minimumTokenBalanceForDividends = 0 * (10**18);
    	}
    	function setRewardToken(address token) external onlyOwner {
    	    _setRewardToken(token);
    	}
    	function setUniswapRouter(address router) external onlyOwner {
    	    _setUniswapRouter(router);
    	}
    	function _transfer(address, address, uint256) internal override pure {
    		require(false, "GroveToken_Dividend_Tracker: No transfers allowed");
    	}
    	function excludeFromDividends(address account) external onlyOwner {
    		require(!excludedFromDividends[account]);
    		excludedFromDividends[account] = true;
    		_setBalance(account, 0);
    		tokenHoldersMap.remove(account);
    		emit ExcludeFromDividends(account);
    	}
    	function setTokenBalanceForDividends(uint256 newValue) external onlyOwner {
    		require(minimumTokenBalanceForDividends != newValue, "GroveToken_Dividend_Tracker: minimumTokenBalanceForDividends already the value of 'newValue'.");
    		minimumTokenBalanceForDividends = newValue;
    	}
    	function getNumberOfTokenHolders() external view returns(uint256) {
    		return tokenHoldersMap.keys.length;
    	}
        function setBalance(address payable account, uint256 newBalance) external onlyOwner {
    		if(excludedFromDividends[account]) {
    			return;
    		}
    		if(newBalance >= minimumTokenBalanceForDividends) {
    			_setBalance(account, newBalance);
    			tokenHoldersMap.set(account, newBalance);
    		}
    		else {
    			_setBalance(account, 0);
    			tokenHoldersMap.remove(account);
    		}
    		processAccount(account, true);
    	}
    	function processAccount(address payable account, bool automatic) public onlyOwner returns (bool) {
    		uint256 amount = _withdrawDividendOfUser(account);
    		if(amount > 0) {
    			lastClaimTimes[account] = block.timestamp;
    			emit Claim(account, amount, automatic);
    			return true;
    		}
    		return false;
    	}
    }