// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/math/SafeMath.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "../boringcrypto/BoringOwnable.sol"; import "../libraries/SafeERC20.sol"; interface IRewarder { using SafeERC20 for IERC20; function onJoeReward(address user, uint256 newLpAmount) external; function pendingTokens(address user) external view returns (uint256 pending); function rewardToken() external view returns (IERC20); } interface IMasterChefJoeV2 { using SafeERC20 for IERC20; struct UserInfo { uint256 amount; // How many LP tokens the user has provided. uint256 rewardDebt; // Reward debt. See explanation below. } struct PoolInfo { IERC20 lpToken; // Address of LP token contract. uint256 allocPoint; // How many allocation points assigned to this poolInfo. SUSHI to distribute per block. uint256 lastRewardTimestamp; // Last block timestamp that SUSHI distribution occurs. uint256 accJoePerShare; // Accumulated SUSHI per share, times 1e12. See below. } function poolInfo(uint256 pid) external view returns (PoolInfo memory); function totalAllocPoint() external view returns (uint256); function deposit(uint256 _pid, uint256 _amount) external; } /** * This is a sample contract to be used in the MasterChefJoeV2 contract for partners to reward * stakers with their native token alongside JOE. * * It assumes no minting rights, so requires a set amount of YOUR_TOKEN to be transferred to this contract prior. * E.g. say you've allocated 100,000 XYZ to the JOE-XYZ farm over 30 days. Then you would need to transfer * 100,000 XYZ and set the block reward accordingly so it's fully distributed after 30 days. * */ contract SimpleRewarderPerBlock is IRewarder, BoringOwnable { using SafeMath for uint256; using SafeERC20 for IERC20; IERC20 public immutable override rewardToken; IERC20 public immutable lpToken; IMasterChefJoeV2 public immutable MC_V2; /// @notice Info of each MCV2 user. /// `amount` LP token amount the user has provided. /// `rewardDebt` The amount of YOUR_TOKEN entitled to the user. struct UserInfo { uint256 amount; uint256 rewardDebt; } /// @notice Info of each MCV2 poolInfo. /// `accTokenPerShare` Amount of YOUR_TOKEN each LP token is worth. /// `lastRewardBlock` The last block YOUR_TOKEN was rewarded to the poolInfo. struct PoolInfo { uint256 accTokenPerShare; uint256 lastRewardBlock; } /// @notice Info of the poolInfo. PoolInfo public poolInfo; /// @notice Info of each user that stakes LP tokens. mapping(address => UserInfo) public userInfo; uint256 public tokenPerBlock; uint256 private constant ACC_TOKEN_PRECISION = 1e12; event OnReward(address indexed user, uint256 amount); event RewardRateUpdated(uint256 oldRate, uint256 newRate); modifier onlyMCV2() { require(msg.sender == address(MC_V2), "onlyMCV2: only MasterChef V2 can call this function"); _; } constructor( IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerBlock, IMasterChefJoeV2 _MCV2 ) public { require(Address.isContract(address(_rewardToken)), "constructor: reward token must be a valid contract"); require(Address.isContract(address(_lpToken)), "constructor: LP token must be a valid contract"); require(Address.isContract(address(_MCV2)), "constructor: MasterChefJoeV2 must be a valid contract"); rewardToken = _rewardToken; lpToken = _lpToken; tokenPerBlock = _tokenPerBlock; MC_V2 = _MCV2; poolInfo = PoolInfo({lastRewardBlock: block.number, accTokenPerShare: 0}); } /// @notice Update reward variables of the given poolInfo. /// @return pool Returns the pool that was updated. function updatePool() public returns (PoolInfo memory pool) { pool = poolInfo; if (block.number > pool.lastRewardBlock) { uint256 lpSupply = lpToken.balanceOf(address(MC_V2)); if (lpSupply > 0) { uint256 blocks = block.number.sub(pool.lastRewardBlock); uint256 tokenReward = blocks.mul(tokenPerBlock); pool.accTokenPerShare = pool.accTokenPerShare.add((tokenReward.mul(ACC_TOKEN_PRECISION) / lpSupply)); } pool.lastRewardBlock = block.number; poolInfo = pool; } } /// @notice Sets the distribution reward rate. This will also update the poolInfo. /// @param _tokenPerBlock The number of tokens to distribute per block function setRewardRate(uint256 _tokenPerBlock) external onlyOwner { updatePool(); uint256 oldRate = tokenPerBlock; tokenPerBlock = _tokenPerBlock; emit RewardRateUpdated(oldRate, _tokenPerBlock); } /// @notice Function called by MasterChefJoeV2 whenever staker claims JOE harvest. Allows staker to also receive a 2nd reward token. /// @param _user Address of user /// @param _lpAmount Number of LP tokens the user has function onJoeReward(address _user, uint256 _lpAmount) external override onlyMCV2 { updatePool(); PoolInfo memory pool = poolInfo; UserInfo storage user = userInfo[_user]; uint256 pending; // if user had deposited if (user.amount > 0) { pending = (user.amount.mul(pool.accTokenPerShare) / ACC_TOKEN_PRECISION).sub(user.rewardDebt); uint256 balance = rewardToken.balanceOf(address(this)); if (pending > balance) { rewardToken.safeTransfer(_user, balance); } else { rewardToken.safeTransfer(_user, pending); } } user.amount = _lpAmount; user.rewardDebt = user.amount.mul(pool.accTokenPerShare) / ACC_TOKEN_PRECISION; emit OnReward(_user, pending); } /// @notice View function to see pending tokens /// @param _user Address of user. /// @return pending reward for a given user. function pendingTokens(address _user) external view override returns (uint256 pending) { PoolInfo memory pool = poolInfo; UserInfo storage user = userInfo[_user]; uint256 accTokenPerShare = poolInfo.accTokenPerShare; uint256 lpSupply = lpToken.balanceOf(address(MC_V2)); if (block.number > poolInfo.lastRewardBlock && lpSupply != 0) { uint256 blocks = block.number.sub(poolInfo.lastRewardBlock); uint256 tokenReward = blocks.mul(tokenPerBlock); accTokenPerShare = accTokenPerShare.add(tokenReward.mul(ACC_TOKEN_PRECISION) / lpSupply); } pending = (user.amount.mul(accTokenPerShare) / ACC_TOKEN_PRECISION).sub(user.rewardDebt); } /// @notice In case rewarder is stopped before emissions finished, this function allows /// withdrawal of remaining tokens. function emergencyWithdraw() public onlyOwner { rewardToken.safeTransfer(address(msg.sender), rewardToken.balanceOf(address(this))); } }