contract BearnTokenERC20Chef is Ownable {
    using SafeMath for uint;
    using SafeERC20 for IERC20;

    // Info of each user.
    struct UserInfo {
        uint amount;     // How many LP tokens the user has provided.
        uint rewardDebt; // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of BFIs
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accBfiePerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accBfiePerShare` (and `lastRewardBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
        uint accumulatedStakingPower; // will accumulate every time user harvest
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;           // Address of LP token contract.
        uint allocPoint;       // How many allocation points assigned to this pool. BFIs to distribute per block.
        uint lastRewardBlock;  // Last block number that BFIs distribution occurs.
        uint accBfiePerShare; // Accumulated BFIs per share, times 1e18. See below.
        bool isStarted; // if lastRewardBlock has passed
    }

    // The BFIE TOKEN!
    BearnTokenERC20 public bfie;

    // BFIE tokens created per block.
    uint public bfiePerBlock;

    // Info of each pool.
    PoolInfo[] public poolInfo;

    // Info of each user that stakes LP tokens.
    mapping (uint => mapping (address => UserInfo)) public userInfo;

    // Total allocation poitns. Must be the sum of all allocation points in all pools.
    uint public totalAllocPoint = 0;

    // The block number when BFIE mining starts.
    uint public startBlock;

    uint public constant BLOCKS_PER_WEEK = 46500;

    address public rewardReferral;

    event Deposit(address indexed user, uint indexed pid, uint amount);
    event Withdraw(address indexed user, uint indexed pid, uint amount);
    event EmergencyWithdraw(address indexed user, uint indexed pid, uint amount);
    event RewardPaid(address indexed user, uint amount);

    constructor(
        BearnTokenERC20 _bfie,
        uint _bfiePerBlock,
        uint _startBlock
    ) public {
        bfie = _bfie;
        bfiePerBlock = _bfiePerBlock; // supposed to be 0.004 (4e16 wei) [set 0.01 at the first 2 weeks for bonus]
        startBlock = _startBlock; // supposed to be 11,360,000 (Mon Nov 30 2020 12:00:00 GMT+0) (https://etherscan.io/block/countdown/11360000)
    }

    function poolLength() external view returns (uint) {
        return poolInfo.length;
    }

    function setBfiePerBlock(uint _bfiePerBlock) public onlyOwner {
        massUpdatePools();
        bfiePerBlock = _bfiePerBlock;
    }

    // Add a new lp to the pool. Can only be called by the owner.
    function add(uint _allocPoint, IERC20 _lpToken, bool _withUpdate, uint _lastRewardBlock) public onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        if (block.number < startBlock) {
            // chef is sleeping
            if (_lastRewardBlock == 0) {
                _lastRewardBlock = startBlock;
            } else {
                if (_lastRewardBlock < startBlock) {
                    _lastRewardBlock = startBlock;
                }
            }
        } else {
            // chef is cooking
            if (_lastRewardBlock == 0 || _lastRewardBlock < block.number) {
                _lastRewardBlock = block.number;
            }
        }
        bool _isStarted = (_lastRewardBlock <= startBlock) || (_lastRewardBlock <= block.number);
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: _lastRewardBlock,
            accBfiePerShare: 0,
            isStarted: _isStarted
        }));
        if (_isStarted) {
            totalAllocPoint = totalAllocPoint.add(_allocPoint);
        }
    }

    // Update the given pool's BFIE allocation point. Can only be called by the owner.
    function set(uint _pid, uint _allocPoint) public onlyOwner {
        massUpdatePools();
        PoolInfo storage pool = poolInfo[_pid];
        if (pool.isStarted) {
            totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(_allocPoint);
        }
        pool.allocPoint = _allocPoint;
    }

    // View function to see pending BFIs on frontend.
    function pendingBearn(uint _pid, address _user) external view returns (uint) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint accBfiePerShare = pool.accBfiePerShare;
        uint lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint _numBlocks = block.number.sub(pool.lastRewardBlock);
            if (totalAllocPoint > 0) {
                uint _bfieReward = _numBlocks.mul(bfiePerBlock).mul(pool.allocPoint).div(totalAllocPoint);
                accBfiePerShare = accBfiePerShare.add(_bfieReward.mul(1e18).div(lpSupply));
            }
        }
        return user.amount.mul(accBfiePerShare).div(1e18).sub(user.rewardDebt);
    }

    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
        uint length = poolInfo.length;
        for (uint pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        if (!pool.isStarted) {
            pool.isStarted = true;
            totalAllocPoint = totalAllocPoint.add(pool.allocPoint);
        }
        if (totalAllocPoint > 0) {
            uint _numBlocks = block.number.sub(pool.lastRewardBlock);
            uint _bfieReward = _numBlocks.mul(bfiePerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            pool.accBfiePerShare = pool.accBfiePerShare.add(_bfieReward.mul(1e18).div(lpSupply));
        }
        pool.lastRewardBlock = block.number;
    }

    // Deposit LP tokens to BearnChef for BFIE allocation.
    function deposit(uint _pid, uint _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint _pending = user.amount.mul(pool.accBfiePerShare).div(1e18).sub(user.rewardDebt);
            if (_pending > 0) {
                safeBfieTransfer(_sender, _pending);
            }
        }
        if (_amount > 0) {
            pool.lpToken.safeTransferFrom(_sender, address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accBfiePerShare).div(1e18);
        emit Deposit(_sender, _pid, _amount);
    }

    // Withdraw LP tokens from BearnChef.
    function withdraw(uint _pid, uint _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint _pending = user.amount.mul(pool.accBfiePerShare).div(1e18).sub(user.rewardDebt);
        if (_pending > 0) {
            safeBfieTransfer(_sender, _pending);
        }
        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(_sender, _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accBfiePerShare).div(1e18);
        emit Withdraw(_sender, _pid, _amount);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        pool.lpToken.safeTransfer(address(msg.sender), user.amount);
        emit EmergencyWithdraw(msg.sender, _pid, user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
    }

    // Safe bfie transfer function, just in case if rounding error causes pool to not have enough BFIs.
    function safeBfieTransfer(address _to, uint _amount) internal {
        uint _bfieBal = bfie.balanceOf(address(this));
        if (_bfieBal > 0) {
            if (_amount > _bfieBal) {
                bfie.transfer(_to, _bfieBal);
            } else {
                bfie.transfer(_to, _amount);
            }
        }
    }

    // This function allows governance to take unsupported tokens out of the contract. This is in an effort to make someone whole, should they seriously mess up.
    // There is no guarantee governance will vote to return these. It also allows for removal of airdropped tokens.
    function governanceRecoverUnsupported(IERC20 _token, uint amount, address to) external onlyOwner {
        if (block.number < startBlock + BLOCKS_PER_WEEK * 24) {// do not allow to drain lpToken if less than 6 months (end of farming period)
            uint length = poolInfo.length;
            for (uint pid = 0; pid < length; ++pid) {
                PoolInfo storage pool = poolInfo[pid];
                // cant take staked asset
                require(_token != pool.lpToken, "!pool.lpToken");
            }
        }
        _token.safeTransfer(to, amount);
    }
}
// BearnTokenERC20Chef is the master of BFIE and he is a fair guy.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
//
// Note that this pool has no minter key as the original MasterChef of Sushi.
// Instead, the governance will mint BFIE and forward to this pool at the beginning.
function updatePool(uint _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        if (!pool.isStarted) {
            pool.isStarted = true;
            totalAllocPoint = totalAllocPoint.add(pool.allocPoint);
        }
        if (totalAllocPoint > 0) {
            uint _numBlocks = block.number.sub(pool.lastRewardBlock);
            uint _bfieReward = _numBlocks.mul(bfiePerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            pool.accBfiePerShare = pool.accBfiePerShare.add(_bfieReward.mul(1e18).div(lpSupply));
        }
        pool.lastRewardBlock = block.number;
    }
// Update reward variables of the given pool to be up-to-date.