contract RocketDropV1point5 is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        uint256 depositStamp;
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;             // Address of LP token contract.
        uint256 lastRewardBlock;    // Last block number that ERC20s distribution occurs.
        uint256 accERC20PerShare;   // Accumulated ERC20s per share, times 1e36.
        IERC20 rewardToken;         // pool specific reward token.
        uint256 startBlock;         // pool specific block number when rewards start
        uint256 endBlock;           // pool specific block number when rewards end
        uint256 rewardPerBlock;     // pool specific reward per block
        uint256 paidOut;            // total paid out by pool
        uint256 tokensStaked;       // allows the same token to be staked across different pools
        uint256 gasAmount;          // eth fee charged on deposits and withdrawals (per pool)
        uint256 minStake;           // minimum tokens allowed to be staked
        uint256 maxStake;           // max tokens allowed to be staked
        address payable partnerTreasury;    // allows eth fee to be split with a partner on transfer
        uint256 partnerPercent;     // eth fee percent of partner split, 2 decimals (ie 10000 = 100.00%, 1002 = 10.02%)
    }

    // extra parameters for pools; optional
    struct PoolExtras {
        uint256 totalStakers;
        uint256 maxStakers;
        uint256 lpTokenFee;         // divide by 1000 ie 150 is 1.5%
        uint256 lockPeriod;         // time in blocks needed before withdrawal
        IERC20 accessToken;
        uint256 accessTokenMin;
        bool accessTokenRequired;
    }

    // default eth fee for deposits and withdrawals
    //uint256 public gasAmount = 2000000000000000;
    uint256 public gasAmount;
    address payable public treasury;
    IERC20 public accessToken;
    uint256 public accessTokenMin;
    bool public accessTokenRequired = false;

    IERC20 public rbxToken;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    PoolExtras[] public poolExtras;
    
    // Info of each user that stakes LP tokens.
    mapping (uint256 => mapping (address => UserInfo)) public userInfo;

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

    constructor(IERC20 rbxTokenAddress) public {
        rbxToken = rbxTokenAddress;
        treasury = msg.sender;
    }

    function rewardPerBlock(uint index) external view returns (uint) {
        return poolInfo[index].rewardPerBlock;
    }

    // Number of LP pools
    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    function currentBlock() external view returns (uint256) {
        return block.number;
    }

    // Fund the farm, increase the end block
    function initialFund(uint256 _pid, uint256 _amount, uint256 _startBlock) public {
        require(poolInfo[_pid].startBlock == 0, "initialFund: initial funding already complete");
        IERC20 erc20;
        erc20 = poolInfo[_pid].rewardToken;

        uint256 startTokenBalance = erc20.balanceOf(address(this));
        erc20.safeTransferFrom(address(msg.sender), address(this), _amount);
        uint256 endTokenBalance = erc20.balanceOf(address(this));
        uint256 trueDepositedTokens = endTokenBalance.sub(startTokenBalance);

        poolInfo[_pid].lastRewardBlock = _startBlock;
        poolInfo[_pid].startBlock = _startBlock;
        poolInfo[_pid].endBlock = _startBlock.add(trueDepositedTokens.div(poolInfo[_pid].rewardPerBlock));
    }

    // Fund the farm, increase the end block
    function fundMore(uint256 _pid, uint256 _amount) public {
        require(block.number < poolInfo[_pid].endBlock, "fundMore: pool closed or use initialFund() first");
        IERC20 erc20;
        erc20 = poolInfo[_pid].rewardToken;

        uint256 startTokenBalance = erc20.balanceOf(address(this));
        erc20.safeTransferFrom(address(msg.sender), address(this), _amount);
        uint256 endTokenBalance = erc20.balanceOf(address(this));
        uint256 trueDepositedTokens = endTokenBalance.sub(startTokenBalance);

        poolInfo[_pid].endBlock += trueDepositedTokens.div(poolInfo[_pid].rewardPerBlock);
    }

    // Add a new lp to the pool. Can only be called by the owner.
    // rewards are calculated per pool, so you can add the same lpToken multiple times
    function add(IERC20 _lpToken, IERC20 _rewardToken, uint256 _rewardPerBlock, bool _withUpdate) public onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        //###
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            lastRewardBlock: 0,
            accERC20PerShare: 0,
            rewardToken: _rewardToken,
            startBlock: 0,
            endBlock: 0,
            rewardPerBlock: _rewardPerBlock,
            paidOut: 0,
            tokensStaked: 0,
            gasAmount: gasAmount,   // defaults to global gas/eth fee
            minStake: 0,
            maxStake: ~uint256(0),
            partnerTreasury: treasury,
            partnerPercent: 0
        }));

        poolExtras.push(PoolExtras({
            totalStakers: 0,
            maxStakers: ~uint256(0),
            lpTokenFee: 0,
            lockPeriod: 0,
            accessTokenRequired: false,
            accessToken: IERC20(address(0)),
            accessTokenMin: 0
        }));
    }

    //####
    // Update the given pool's ERC20 reward per block. Can only be called by the owner.
    function set(uint256 _pid, uint256 _rewardPerBlock, bool _withUpdate) public onlyOwner {
        if (_withUpdate) {
            updatePool(_pid);
        }
        poolInfo[_pid].rewardPerBlock = _rewardPerBlock;
        updatePool(_pid);
    }

    // Pool adjustment functions
    function minStake(uint256 _pid, uint256 _minStake) public onlyOwner {
        poolInfo[_pid].minStake = _minStake;
    }

    function maxStake(uint256 _pid, uint256 _maxStake) public onlyOwner {
        poolInfo[_pid].maxStake = _maxStake;
    }

    function maxStakersAdj(uint256 _pid, uint256 _maxStakers) public onlyOwner {
        poolExtras[_pid].maxStakers = _maxStakers;
    }

    function lpTokenFeeAdj(uint256 _pid, uint256 _lpTokenFee) public onlyOwner {
        poolExtras[_pid].lpTokenFee = _lpTokenFee;
    }

    function lockPeriodAdj(uint256 _pid, uint256 _lockPeriod) public onlyOwner {
        poolExtras[_pid].lockPeriod = _lockPeriod;
    }

    function poolAccessTokenReq(uint256 _pid, bool _accessTokenRequired) public onlyOwner {
        poolExtras[_pid].accessTokenRequired = _accessTokenRequired;
    }

    function poolAccessTokenAddy(uint256 _pid, IERC20 _accessToken) public onlyOwner {
        poolExtras[_pid].accessToken = _accessToken;
    }

    function poolAccessTokenMin(uint256 _pid, uint256 _accessTokenMin) public onlyOwner {
        poolExtras[_pid].accessTokenMin = _accessTokenMin;
    }
    // END Pool adjustment functions

    // View function to see deposited LP for a user.
    function deposited(uint256 _pid, address _user) external view returns (uint256) {
        UserInfo storage user = userInfo[_pid][_user];
        return user.amount;
    }

    // View function to see pending ERC20s for a user.
    function pending(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo memory pool = poolInfo[_pid];
        UserInfo memory user = userInfo[_pid][_user];
        uint256 accERC20PerShare = pool.accERC20PerShare;
        
        uint256 lpSupply = pool.tokensStaked;

        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 lastBlock = block.number < pool.endBlock ? block.number : pool.endBlock;
            uint256 nrOfBlocks = lastBlock.sub(pool.lastRewardBlock);
            uint256 erc20Reward = nrOfBlocks.mul(pool.rewardPerBlock);
            accERC20PerShare = accERC20PerShare.add(erc20Reward.mul(1e36).div(lpSupply));
        }

        return user.amount.mul(accERC20PerShare).div(1e36).sub(user.rewardDebt);
    }

    // View function for total reward the farm has yet to pay out.
    function totalPending(uint256 _pid) external view returns (uint256) {
        if (block.number <= poolInfo[_pid].startBlock) {
            return 0;
        }

        uint256 lastBlock = block.number < poolInfo[_pid].endBlock ? block.number : poolInfo[_pid].endBlock;
        return poolInfo[_pid].rewardPerBlock.mul(lastBlock - poolInfo[_pid].startBlock).sub(poolInfo[_pid].paidOut);
    }

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

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        uint256 lastBlock = block.number < pool.endBlock ? block.number : pool.endBlock;

        if (lastBlock <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = pool.tokensStaked;
        if (lpSupply == 0) {
            pool.lastRewardBlock = lastBlock;
            return;
        }

        uint256 nrOfBlocks = lastBlock.sub(pool.lastRewardBlock);
        uint256 erc20Reward = nrOfBlocks.mul(pool.rewardPerBlock);

        pool.accERC20PerShare = pool.accERC20PerShare.add(erc20Reward.mul(1e36).div(lpSupply));
        pool.lastRewardBlock = lastBlock;
    }

    // Deposit LP tokens to VendingMachine for ERC20 allocation.
    function deposit(uint256 _pid, uint256 _amount) public payable {
        if(accessTokenRequired){
            require(accessToken.balanceOf(msg.sender) >= accessTokenMin, 'Must have minimum amount of access token!');
        }
        PoolExtras storage poolEx = poolExtras[_pid];

        if(poolEx.accessTokenRequired){
            require(poolEx.accessToken.balanceOf(msg.sender) >= poolEx.accessTokenMin, 'Must have minimum amount of access token!');
        }
        require(poolEx.totalStakers < poolEx.maxStakers, 'Max stakers reached!');

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

        uint256 poolGasAmount = pool.gasAmount;
        require(msg.value >= poolGasAmount, 'Correct gas amount must be sent!');
        require(_amount >= pool.minStake && (_amount.add(user.amount)) <= pool.maxStake, 'Min/Max stake required!');

        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);
            if(pendingAmount > 0)
                erc20Transfer(msg.sender, _pid, pendingAmount);
        }

        uint256 startTokenBalance = pool.lpToken.balanceOf(address(this));
        pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        uint256 endTokenBalance = pool.lpToken.balanceOf(address(this));
        uint256 depositFee = poolEx.lpTokenFee.mul(endTokenBalance).div(1000);
        uint256 trueDepositedTokens = endTokenBalance.sub(startTokenBalance).sub(depositFee);

        user.amount = user.amount.add(trueDepositedTokens);
        user.depositStamp = block.number;
        pool.tokensStaked = pool.tokensStaked.add(trueDepositedTokens);
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        
        // remit eth fee according to partner status 
        if (pool.partnerPercent == 0) {
            treasury.transfer(msg.value);
        } else {
            uint256 totalAmount = msg.value;
            uint256 partnerAmount = totalAmount.mul(pool.partnerPercent).div(10000);
            uint256 treasuryAmount = totalAmount.sub(partnerAmount);
            treasury.transfer(treasuryAmount);
            pool.partnerTreasury.transfer(partnerAmount);
        }
        
        poolEx.totalStakers = poolEx.totalStakers.add(1);
        emit Deposit(msg.sender, _pid, _amount);
    }

    // Withdraw LP tokens from VendingMachine.
    function withdraw(uint256 _pid, uint256 _amount) public payable {
        if(accessTokenRequired){
            require(accessToken.balanceOf(msg.sender) >= accessTokenMin, 'Must have minimum amount of access token!');
        }
        PoolExtras storage poolEx = poolExtras[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        uint256 poolGasAmount = pool.gasAmount;
        require(msg.value >= poolGasAmount, 'Correct gas amount must be sent!');

        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: can't withdraw more than deposit");
        updatePool(_pid);
        uint256 pendingAmount = user.amount.mul(pool.accERC20PerShare).div(1e36).sub(user.rewardDebt);

        if(pendingAmount > 0)
            erc20Transfer(msg.sender, _pid, pendingAmount);
            
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accERC20PerShare).div(1e36);
        
        if(_amount > 0){
            require(user.depositStamp.add(poolEx.lockPeriod) <= block.number,'Lock period not fulfilled');
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
            pool.tokensStaked = pool.tokensStaked.sub(_amount);
        }

        // remit eth fee according to partner status 
        if (pool.partnerPercent == 0) {
            treasury.transfer(msg.value);
        } else {
            uint256 totalAmount = msg.value;
            uint256 partnerAmount = totalAmount.mul(pool.partnerPercent).div(10000);
            uint256 treasuryAmount = totalAmount.sub(partnerAmount);
            treasury.transfer(treasuryAmount);
            pool.partnerTreasury.transfer(partnerAmount);
        }

        if(user.amount == 0){
            poolEx.totalStakers = poolEx.totalStakers.sub(1);
        }
        emit Withdraw(msg.sender, _pid, _amount);
    }

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

    // Transfer ERC20 and update the required ERC20 to payout all rewards
    function erc20Transfer(address _to, uint256 _pid, uint256 _amount) internal {
        IERC20 erc20;
        erc20 = poolInfo[_pid].rewardToken;
        erc20.transfer(_to, _amount);
        poolInfo[_pid].paidOut += _amount;
    }
    
    // adjust default/global gas fee
    function adjustGasGlobal(uint256 newgas) public onlyOwner {
        gasAmount = newgas;
    }

    // access token settings
    function changeAccessToken(IERC20 newToken) public onlyOwner {
        accessToken = newToken;
    }
    function changeAccessMin(uint256 newMin) public onlyOwner {
        accessTokenMin = newMin;
    }
    function changeAccessTknReq(bool setting) public onlyOwner {
        accessTokenRequired = setting;
    }

    // adjust pool gas/eth fee
    function adjustPoolGas(uint256 _pid, uint256 newgas) public onlyOwner {
        poolInfo[_pid].gasAmount = newgas;
    }

    function adjustBlockReward(uint256 _pid, uint256 newReward) public onlyOwner {
        poolInfo[_pid].rewardPerBlock = newReward;
    }

    function adjustEndBlock(uint256 _pid, uint256 newBlock) public onlyOwner {
        poolInfo[_pid].endBlock = newBlock;
    }

    function adjustLastBlock(uint256 _pid, uint256 newBlock) public onlyOwner {
        poolInfo[_pid].lastRewardBlock = newBlock;
    }
    
    function withdrawAnyToken(address _recipient, address _ERC20address, uint256 _amount) public onlyOwner returns(bool) {
        IERC20(_ERC20address).transfer(_recipient, _amount); //use of the _ERC20 traditional transfer
        return true;
    }

    // change global treasury
    function changeTreasury(address payable newTreasury) public onlyOwner {
        treasury = newTreasury;
    }

    function changePartnerTreasury(uint256 _pid, address payable newTreasury) public onlyOwner {
        poolInfo[_pid].partnerTreasury = newTreasury;
    }
    
    function changePartnerPercent(uint256 _pid, uint256 newPercent) public onlyOwner {
        poolInfo[_pid].partnerPercent = newPercent;
    }

    function transfer() public onlyOwner {
        treasury.transfer(address(this).balance);
    }
}
function initialFund(uint256 _pid, uint256 _amount, uint256 _startBlock) public {
    require(poolInfo[_pid].startBlock == 0, "initialFund: initial funding already complete");
    IERC20 erc20;
    erc20 = poolInfo[_pid].rewardToken;

    uint256 startTokenBalance = erc20.balanceOf(address(this));
    erc20.safeTransferFrom(address(msg.sender), address(this), _amount);
    uint256 endTokenBalance = erc20.balanceOf(address(this));
    uint256 trueDepositedTokens = endTokenBalance.sub(startTokenBalance);

    poolInfo[_pid].lastRewardBlock = _startBlock;
    poolInfo[_pid].startBlock = _startBlock;
    poolInfo[_pid].endBlock = _startBlock.add(trueDepositedTokens.div(poolInfo[_pid].rewardPerBlock));
}

// Fund the farm, increase the end block