TombBasedRewardPools
274 linhas
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
// Note that this pool has no minter key of tSHARE (rewards).
// Note that this pool has no minter key of bSHARE (rewards).
// Instead, the governance will call tSHARE distributeReward method and send reward to this pool at the beginning.
// Instead, the governance will call bSHARE distributeReward method and send reward to this pool at the beginning.
contract TShareRewardPool {
contract BShareRewardPool {
    using SafeMath for uint256;
    using SafeMath for uint256;
    using SafeERC20 for IERC20;
    using SafeERC20 for IERC20;
    // governance
    // governance
    address public operator;
    address public operator;
    // Info of each user.
    // Info of each user.
    struct UserInfo {
    struct UserInfo {
        uint256 amount; // How many LP tokens the user has provided.
        uint256 amount; // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
        uint256 rewardDebt; // Reward debt. See explanation below.
    }
    }
    // Info of each pool.
    // Info of each pool.
    struct PoolInfo {
    struct PoolInfo {
        IERC20 token; // Address of LP token contract.
        IERC20 token; // Address of LP token contract.
        uint256 allocPoint; // How many allocation points assigned to this pool. tSHAREs to distribute per block.
        uint256 allocPoint; // How many allocation points assigned to this pool. bSHAREs to distribute per block.
        uint256 lastRewardTime; // Last time that tSHAREs distribution occurs.
        uint256 lastRewardTime; // Last time that bSHAREs distribution occurs.
        uint256 accTSharePerShare; // Accumulated tSHAREs per share, times 1e18. See below.
        uint256 accBSharePerShare; // Accumulated bSHAREs per share, times 1e18. See below.
        bool isStarted; // if lastRewardTime has passed
        bool isStarted; // if lastRewardTime has passed
    }
    }
    IERC20 public tshare;
    IERC20 public bshare;
    // Info of each pool.
    // Info of each pool.
    PoolInfo[] public poolInfo;
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    // Info of each user that stakes LP tokens.
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    // Total allocation points. Must be the sum of all allocation points in all pools.
    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;
    uint256 public totalAllocPoint = 0;
    // The time when tSHARE mining starts.
    // The time when bSHARE mining starts.
    uint256 public poolStartTime;
    uint256 public poolStartTime;
    // The time when tSHARE mining ends.
    // The time when bSHARE mining ends.
    uint256 public poolEndTime;
    uint256 public poolEndTime;
    uint256 public tSharePerSecond = 0.00186122 ether; // 59500 tshare / (370 days * 24h * 60min * 60s)
    address public daoFundAddress;
    uint256 public runningTime = 370 days; // 370 days
    uint256 public constant TOTAL_REWARDS = 59500 ether;
    uint256 public bSharePerSecond = 0.003486 ether; // 50000 bshare / (166 days * 24h * 60min * 60s)
    uint256 public runningTime = 166 days; // 166 days
    uint256 public constant TOTAL_REWARDS = 50000 ether;
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(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);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event RewardPaid(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 amount);
    constructor(
    constructor(
        address _tshare,
        address _bshare,
        address _daoFund,
        uint256 _poolStartTime
        uint256 _poolStartTime
    ) public {
    ) {
        require(block.timestamp < _poolStartTime, "late");
        require(block.timestamp < _poolStartTime, "pool cant be started in the past");
        if (_tshare != address(0)) tshare = IERC20(_tshare);
        if (_bshare != address(0)) bshare = IERC20(_bshare);
        if(_daoFund != address(0)) daoFundAddress = _daoFund;
        poolStartTime = _poolStartTime;
        poolStartTime = _poolStartTime;
        poolEndTime = poolStartTime + runningTime;
        poolEndTime = poolStartTime + runningTime;
        operator = msg.sender;
        operator = msg.sender;
    }
    }
    modifier onlyOperator() {
    modifier onlyOperator() {
        require(operator == msg.sender, "TShareRewardPool: caller is not the operator");
        require(operator == msg.sender, "BShareRewardPool: caller is not the operator");
        _;
        _;
    }
    }
    function checkPoolDuplicate(IERC20 _token) internal view {
    function checkPoolDuplicate(IERC20 _token) internal view {
        uint256 length = poolInfo.length;
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
        for (uint256 pid = 0; pid < length; ++pid) {
            require(poolInfo[pid].token != _token, "TShareRewardPool: existing pool?");
            require(poolInfo[pid].token != _token, "BShareRewardPool: existing pool?");
        }
        }
    }
    }
    // Add a new lp to the pool. Can only be called by the owner.
    // Add new lp to the pool. Can only be called by operator.
    function add(
    function add(
        uint256 _allocPoint,
        uint256 _allocPoint,
        IERC20 _token,
        IERC20 _token,
        bool _withUpdate,
        bool _withUpdate,
        uint256 _lastRewardTime
        uint256 _lastRewardTime
    ) public onlyOperator {
    ) public onlyOperator {
        checkPoolDuplicate(_token);
        checkPoolDuplicate(_token);
        if (_withUpdate) {
        if (_withUpdate) {
            massUpdatePools();
            massUpdatePools();
        }
        }
        if (block.timestamp < poolStartTime) {
        if (block.timestamp < poolStartTime) {
            // chef is sleeping
            // chef is sleeping
            if (_lastRewardTime == 0) {
            if (_lastRewardTime == 0) {
                _lastRewardTime = poolStartTime;
                _lastRewardTime = poolStartTime;
            } else {
            } else {
                if (_lastRewardTime < poolStartTime) {
                if (_lastRewardTime < poolStartTime) {
                    _lastRewardTime = poolStartTime;
                    _lastRewardTime = poolStartTime;
                }
                }
            }
            }
        } else {
        } else {
            // chef is cooking
            // chef is cooking
            if (_lastRewardTime == 0 || _lastRewardTime < block.timestamp) {
            if (_lastRewardTime == 0 || _lastRewardTime < block.timestamp) {
                _lastRewardTime = block.timestamp;
                _lastRewardTime = block.timestamp;
            }
            }
        }
        }
        bool _isStarted =
        bool _isStarted = (_lastRewardTime <= poolStartTime) || (_lastRewardTime <= block.timestamp);
        (_lastRewardTime <= poolStartTime) ||
        (_lastRewardTime <= block.timestamp);
        poolInfo.push(PoolInfo({
        poolInfo.push(PoolInfo({
            token : _token,
            token: _token,
            allocPoint : _allocPoint,
            allocPoint: _allocPoint,
            lastRewardTime : _lastRewardTime,
            lastRewardTime: _lastRewardTime,
            accTSharePerShare : 0,
            accBSharePerShare: 0,
            isStarted : _isStarted
            isStarted: _isStarted
            }));
        }));
        if (_isStarted) {
        if (_isStarted) {
            totalAllocPoint = totalAllocPoint.add(_allocPoint);
            totalAllocPoint = totalAllocPoint.add(_allocPoint);
        }
        }
    }
    }
    // Update the given pool's tSHARE allocation point. Can only be called by the owner.
    // starting allocations for our pools
    // BASED-TOMB LP: 21500 $BSHARE
    // BSHARE-TOMB LP: 21000 $BSHARE
    // TEAM: 4500 $BSHARE
    // CURVE STABLES LP (OR GEIST STABLES LP): 3000 $BSHARE
    // Update the given pool's bSHARE allocation point. Can only be called by the operator.
    // @allocPoints for TEAM can NOT be altered after added - PID 2
    // @allocPoints for main LP pools can NOT be smaller than 12,000
    function set(uint256 _pid, uint256 _allocPoint) public onlyOperator {
    function set(uint256 _pid, uint256 _allocPoint) public onlyOperator {
        massUpdatePools();
        massUpdatePools();
        require (_pid != 2, "CAN NOT ADJUST TEAM ALLOCATIONS");
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        if (pool.isStarted) {
            totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(
        if (_pid == 0 || _pid == 1) {
                _allocPoint
            require(_allocPoint >= 12000 * 10**18, "out of range"); // >= allocations for lp pools cant be less than 12,000
            );
            if (pool.isStarted) {
                totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(_allocPoint);
            }
        } else if (_pid > 2) {
            if (pool.isStarted) {
                totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(_allocPoint);
            }
        }
        }
        pool.allocPoint = _allocPoint;
        pool.allocPoint = _allocPoint;
    }
    }
    // Return accumulate rewards over the given _from to _to block.
    // Return accumulate rewards over the given _from to _to block.
    function getGeneratedReward(uint256 _fromTime, uint256 _toTime) public view returns (uint256) {
    function getGeneratedReward(uint256 _fromTime, uint256 _toTime) public view returns (uint256) {
        if (_fromTime >= _toTime) return 0;
        if (_fromTime >= _toTime) return 0;
        if (_toTime >= poolEndTime) {
        if (_toTime >= poolEndTime) {
            if (_fromTime >= poolEndTime) return 0;
            if (_fromTime >= poolEndTime) return 0;
            if (_fromTime <= poolStartTime) return poolEndTime.sub(poolStartTime).mul(tSharePerSecond);
            if (_fromTime <= poolStartTime) return poolEndTime.sub(poolStartTime).mul(bSharePerSecond);
            return poolEndTime.sub(_fromTime).mul(tSharePerSecond);
            return poolEndTime.sub(_fromTime).mul(bSharePerSecond);
        } else {
        } else {
            if (_toTime <= poolStartTime) return 0;
            if (_toTime <= poolStartTime) return 0;
            if (_fromTime <= poolStartTime) return _toTime.sub(poolStartTime).mul(tSharePerSecond);
            if (_fromTime <= poolStartTime) return _toTime.sub(poolStartTime).mul(bSharePerSecond);
            return _toTime.sub(_fromTime).mul(tSharePerSecond);
            return _toTime.sub(_fromTime).mul(bSharePerSecond);
        }
        }
    }
    }
    // View function to see pending tSHAREs on frontend.
    // View function to see pending bSHAREs on frontend.
    function pendingShare(uint256 _pid, address _user) external view returns (uint256) {
    function pendingShare(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accTSharePerShare = pool.accTSharePerShare;
        uint256 accBSharePerShare = pool.accBSharePerShare;
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        if (block.timestamp > pool.lastRewardTime && tokenSupply != 0) {
        if (block.timestamp > pool.lastRewardTime && tokenSupply != 0) {
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _tshareReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            uint256 _bshareReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            accTSharePerShare = accTSharePerShare.add(_tshareReward.mul(1e18).div(tokenSupply));
            accBSharePerShare = accBSharePerShare.add(_bshareReward.mul(1e18).div(tokenSupply));
        }
        }
        return user.amount.mul(accTSharePerShare).div(1e18).sub(user.rewardDebt);
        return user.amount.mul(accBSharePerShare).div(1e18).sub(user.rewardDebt);
    }
    }
    // Update reward variables for all pools. Be careful of gas spending!
    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
            updatePool(pid);
        }
        }
    }
    }
    // Update reward variables of the given pool to be up-to-date.
    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        if (block.timestamp <= pool.lastRewardTime) {
        if (block.timestamp <= pool.lastRewardTime) {
            return;
            return;
        }
        }
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        if (tokenSupply == 0) {
        if (tokenSupply == 0) {
            pool.lastRewardTime = block.timestamp;
            pool.lastRewardTime = block.timestamp;
            return;
            return;
        }
        }
        if (!pool.isStarted) {
        if (!pool.isStarted) {
            pool.isStarted = true;
            pool.isStarted = true;
            totalAllocPoint = totalAllocPoint.add(pool.allocPoint);
            totalAllocPoint = totalAllocPoint.add(pool.allocPoint);
        }
        }
        if (totalAllocPoint > 0) {
        if (totalAllocPoint > 0) {
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _tshareReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            uint256 _bshareReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            pool.accTSharePerShare = pool.accTSharePerShare.add(_tshareReward.mul(1e18).div(tokenSupply));
            pool.accBSharePerShare = pool.accBSharePerShare.add(_bshareReward.mul(1e18).div(tokenSupply));
        }
        }
        pool.lastRewardTime = block.timestamp;
        pool.lastRewardTime = block.timestamp;
    }
    }
    // Deposit LP tokens.
    // Deposit LP tokens.
    function deposit(uint256 _pid, uint256 _amount) public {
    function deposit(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        UserInfo storage user = userInfo[_pid][_sender];
        updatePool(_pid);
        updatePool(_pid);
        if (user.amount > 0) {
        if (user.amount > 0) {
            uint256 _pending = user.amount.mul(pool.accTSharePerShare).div(1e18).sub(user.rewardDebt);
            uint256 _pending = user.amount.mul(pool.accBSharePerShare).div(1e18).sub(user.rewardDebt);
            if (_pending > 0) {
            if (_pending > 0) {
                safeTShareTransfer(_sender, _pending);
                safeBShareTransfer(_sender, _pending);
                emit RewardPaid(_sender, _pending);
                emit RewardPaid(_sender, _pending);
            }
            }
        }
        }
        if (_amount > 0) {
        if (_amount > 0 && _pid > 2) {
            pool.token.safeTransferFrom(_sender, address(this), _amount);
            uint256 depositDebt = _amount.mul(20).div(10000);
            user.amount = user.amount.add(_amount.sub(depositDebt));
            pool.token.safeTransfer(daoFundAddress, depositDebt);
        } else if (_amount > 0 && _pid <= 2) {
            pool.token.safeTransferFrom(_sender, address(this), _amount);
            pool.token.safeTransferFrom(_sender, address(this), _amount);
            user.amount = user.amount.add(_amount);
            user.amount = user.amount.add(_amount);
        }
        }
        user.rewardDebt = user.amount.mul(pool.accTSharePerShare).div(1e18);
        user.rewardDebt = user.amount.mul(pool.accBSharePerShare).div(1e18);
        emit Deposit(_sender, _pid, _amount);
        emit Deposit(_sender, _pid, _amount);
    }
    }
    // Withdraw LP tokens.
    // Withdraw LP tokens.
    function withdraw(uint256 _pid, uint256 _amount) public {
    function withdraw(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        UserInfo storage user = userInfo[_pid][_sender];
        require(user.amount >= _amount, "withdraw: not good");
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        updatePool(_pid);
        uint256 _pending = user.amount.mul(pool.accTSharePerShare).div(1e18).sub(user.rewardDebt);
        uint256 _pending = user.amount.mul(pool.accBSharePerShare).div(1e18).sub(user.rewardDebt);
        if (_pending > 0) {
        if (_pending > 0) {
            safeTShareTransfer(_sender, _pending);
            safeBShareTransfer(_sender, _pending);
            emit RewardPaid(_sender, _pending);
            emit RewardPaid(_sender, _pending);
        }
        }
        if (_amount > 0) {
        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            user.amount = user.amount.sub(_amount);
            pool.token.safeTransfer(_sender, _amount);
            pool.token.safeTransfer(_sender, _amount);
        }
        }
        user.rewardDebt = user.amount.mul(pool.accTSharePerShare).div(1e18);
        user.rewardDebt = user.amount.mul(pool.accBSharePerShare).div(1e18);
        emit Withdraw(_sender, _pid, _amount);
        emit Withdraw(_sender, _pid, _amount);
    }
    }
    // Withdraw without caring about rewards. EMERGENCY ONLY.
    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint256 _pid) public {
    function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 _amount = user.amount;
        uint256 _amount = user.amount;
        user.amount = 0;
        user.amount = 0;
        user.rewardDebt = 0;
        user.rewardDebt = 0;
        pool.token.safeTransfer(msg.sender, _amount);
        pool.token.safeTransfer(msg.sender, _amount);
        emit EmergencyWithdraw(msg.sender, _pid, _amount);
        emit EmergencyWithdraw(msg.sender, _pid, _amount);
    }
    }
    // Safe tshare transfer function, just in case if rounding error causes pool to not have enough tSHAREs.
    // Safe bshare transfer function, just in case if rounding error causes pool to not have enough bSHAREs.
    function safeTShareTransfer(address _to, uint256 _amount) internal {
    function safeBShareTransfer(address _to, uint256 _amount) internal {
        uint256 _tshareBal = tshare.balanceOf(address(this));
        uint256 _bshareBal = bshare.balanceOf(address(this));
        if (_tshareBal > 0) {
        if (_bshareBal > 0) {
            if (_amount > _tshareBal) {
            if (_amount > _bshareBal) {
                tshare.safeTransfer(_to, _tshareBal);
                bshare.safeTransfer(_to, _bshareBal);
            } else {
            } else {
                tshare.safeTransfer(_to, _amount);
                bshare.safeTransfer(_to, _amount);
            }
            }
        }
        }
    }
    }
    function setOperator(address _operator) external onlyOperator {
    function setOperator(address _operator) external onlyOperator {
        operator = _operator;
        operator = _operator;
    }
    }
    function governanceRecoverUnsupported(IERC20 _token, uint256 amount, address to) external onlyOperator {
    function governanceRecoverUnsupported(IERC20 _token, uint256 amount, address to) external onlyOperator {
        if (block.timestamp < poolEndTime + 90 days) {
        if (block.timestamp < poolEndTime + 90 days) {
            // do not allow to drain core token (tSHARE or lps) if less than 90 days after pool ends
            // do not allow to drain core token (tSHARE or lps) if less than 90 days after pool ends
            require(_token != tshare, "tshare");
            require(_token != bshare, "bshare");
            uint256 length = poolInfo.length;
            uint256 length = poolInfo.length;
            for (uint256 pid = 0; pid < length; ++pid) {
            for (uint256 pid = 0; pid < length; ++pid) {
                PoolInfo storage pool = poolInfo[pid];
                PoolInfo storage pool = poolInfo[pid];
                require(_token != pool.token, "pool.token");
                require(_token != pool.token, "pool.token");
            }
            }
        }
        }
        _token.safeTransfer(to, amount);
        _token.safeTransfer(to, amount);
    }
    }
}
}