// 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 "@openzeppelin/contracts/access/Ownable.sol"; import "../libraries/SafeERC20.sol"; import "../interfaces/IRewarder.sol"; import "hardhat/console.sol"; interface IMasterChef { struct PoolInfo { uint256 allocPoint; // How many allocation points assigned to this pool. JOE to distribute per block. } function deposit(uint256 _pid, uint256 _amount) external; function poolInfo(uint256 pid) external view returns (IMasterChef.PoolInfo memory); function totalAllocPoint() external view returns (uint256); } 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 number 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 the project already has an existing MasterChef-style farm contract. * In which case, the init() function is called to deposit a dummy token into one * of the MasterChef farms so this contract can accrue rewards from that farm. * The contract then transfers the reward token to the user on each call to * onJoeReward(). * */ contract MasterChefRewarderPerBlock is IRewarder, Ownable { using SafeMath for uint256; using SafeERC20 for IERC20; IERC20 public immutable override rewardToken; IERC20 public immutable lpToken; uint256 public immutable MCV1_pid; IMasterChef public immutable MCV1; IMasterChefJoeV2 public immutable MCV2; /// @notice Info of each MCV2 user. /// `amount` LP token amount the user has provided. /// `rewardDebt` The amount of JOE entitled to the user. struct UserInfo { uint256 amount; uint256 rewardDebt; } /// @notice Info of each MCV2 poolInfo. /// `accTokenPerShare` Amount of JOE each LP token is worth. /// `lastRewardBlock` The last block JOE was rewarded to the poolInfo. struct PoolInfo { uint256 accTokenPerShare; uint256 lastRewardBlock; uint256 allocPoint; } /// @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); event AllocPointUpdated(uint256 oldAllocPoint, uint256 newAllocPoint); modifier onlyMCV2() { require(msg.sender == address(MCV2), "onlyMCV2: only MasterChef V2 can call this function"); _; } constructor( IERC20 _rewardToken, IERC20 _lpToken, uint256 _tokenPerBlock, uint256 _allocPoint, uint256 _MCV1_pid, IMasterChef _MCV1, 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(_MCV1)), "constructor: MasterChef must be a valid contract"); require(Address.isContract(address(_MCV2)), "constructor: MasterChefJoeV2 must be a valid contract"); rewardToken = _rewardToken; lpToken = _lpToken; tokenPerBlock = _tokenPerBlock; MCV1_pid = _MCV1_pid; MCV1 = _MCV1; MCV2 = _MCV2; poolInfo = PoolInfo({lastRewardBlock: block.number, accTokenPerShare: 0, allocPoint: _allocPoint}); } /// @notice Deposits a dummy token to a MaterChefV1 farm so that this contract can claim reward tokens. /// @param dummyToken The address of the dummy ERC20 token to deposit into MCV1. function init(IERC20 dummyToken) external { uint256 balance = dummyToken.balanceOf(msg.sender); require(balance > 0, "init: Balance must exceed 0"); dummyToken.safeTransferFrom(msg.sender, balance); dummyToken.approve(address(MCV1), balance); MCV1.deposit(MCV1_pid, balance); } /// @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(MCV2)); if (lpSupply > 0) { uint256 blocks = block.number.sub(pool.lastRewardBlock); uint256 tokenReward = blocks.mul(tokenPerBlock).mul(pool.allocPoint).div(MCV1.totalAllocPoint()); 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 Sets the allocation point. THis will also update the poolInfo. /// @param _allocPoint The new allocation point of the pool function setAllocPoint(uint256 _allocPoint) external onlyOwner { updatePool(); uint256 oldAllocPoint = poolInfo.allocPoint; poolInfo.allocPoint = _allocPoint; emit AllocPointUpdated(oldAllocPoint, _allocPoint); } /// @notice Claims reward tokens from MCV1 farm. function harvestFromMasterChefV1() public { MCV1.deposit(MCV1_pid, 0); } /// @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 pendingBal; // if user had deposited if (user.amount > 0) { harvestFromMasterChefV1(); pendingBal = (user.amount.mul(pool.accTokenPerShare) / ACC_TOKEN_PRECISION).sub(user.rewardDebt); uint256 rewardBal = rewardToken.balanceOf(address(this)); if (pendingBal > rewardBal) { rewardToken.safeTransfer(_user, rewardBal); } else { rewardToken.safeTransfer(_user, pendingBal); } } user.amount = _lpAmount; user.rewardDebt = user.amount.mul(pool.accTokenPerShare) / ACC_TOKEN_PRECISION; emit OnReward(_user, pendingBal); } /// @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 = pool.accTokenPerShare; uint256 lpSupply = lpToken.balanceOf(address(MCV2)); if (block.number > pool.lastRewardBlock && lpSupply != 0) { uint256 blocks = block.number.sub(pool.lastRewardBlock); uint256 tokenReward = blocks.mul(tokenPerBlock).mul(pool.allocPoint).div(MCV1.totalAllocPoint()); accTokenPerShare = accTokenPerShare.add(tokenReward.mul(ACC_TOKEN_PRECISION) / lpSupply); } pending = (user.amount.mul(accTokenPerShare) / ACC_TOKEN_PRECISION).sub(user.rewardDebt); } }