MasterChef (Sushiswap) and StablesPool
264 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
pragma solidity 0.6.12;
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/SafeERC20.sol";
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./SushiToken.sol";
interface IMigratorChef {
// Perform LP token migration from legacy UniswapV2 to SushiSwap.
// Take the current LP token address and return the new LP token address.
// Migrator should have full access to the caller's LP token.
// Return the new LP token address.
//
// XXX Migrator must have allowance access to UniswapV2 LP tokens.
// SushiSwap must mint EXACTLY the same amount of SushiSwap LP tokens or
// else something bad will happen. Traditional UniswapV2 does not
// do that so be careful!
function migrate(IERC20 token) external returns (IERC20);
}
// MasterChef is the master of Sushi. He can make Sushi and he is a fair guy.
// Note that this pool has no minter key of BSD (rewards).
//
// Instead, the governance will call BSD.distributeReward and send reward to this pool at the beginning.
// Note that it's ownable and the owner wields tremendous power. The ownership
contract StablesPool {
// will be transferred to a governance smart contract once SUSHI is sufficiently
// distributed and the community can show to govern itself.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
contract MasterChef is Ownable {
using SafeMath for uint256;
using SafeMath for uint256;
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
address public governance;
// 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.
//
//
// We do some fancy math here. Basically, any point in time, the amount of SUSHIs
// We do some fancy math here. Basically, any point in time, the amount of BSDs
// entitled to a user but is pending to be distributed is:
// entitled to a user but is pending to be distributed is:
//
//
// pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
// pending reward = (user.amount * pool.accBsdPerShare) - user.rewardDebt
//
//
// Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
// Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
// 1. The pool's `accSushiPerShare` (and `lastRewardBlock`) gets updated.
// 1. The pool's `accBsdPerShare` (and `lastRewardBlock`) gets updated.
// 2. User receives the pending reward sent to his/her address.
// 2. User receives the pending reward sent to his/her address.
// 3. User's `amount` gets updated.
// 3. User's `amount` gets updated.
// 4. User's `rewardDebt` gets updated.
// 4. User's `rewardDebt` gets updated.
uint256 accumulatedStakingPower; // will accumulate every time user harvest
}
}
// Info of each pool.
// Info of each pool.
struct PoolInfo {
struct PoolInfo {
IERC20 lpToken; // Address of LP token contract.
IERC20 lpToken; // Address of LP token contract.
uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.
uint256 lastRewardBlock; // Last block number that BSDs distribution occurs.
uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.
uint256 accBsdPerShare; // Accumulated BSDs per share, times 1e18. See below.
uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.
}
}
// The SUSHI TOKEN!
// The BSD TOKEN!
SushiToken public sushi;
IERC20 public bsd = IERC20(0x003e0af2916e598Fa5eA5Cb2Da4EDfdA9aEd9Fde);
// Dev address.
address public devaddr;
// Block number when bonus SUSHI period ends.
uint256 public bonusEndBlock;
// SUSHI tokens created per block.
uint256 public sushiPerBlock;
// Bonus muliplier for early sushi makers.
uint256 public constant BONUS_MULTIPLIER = 10;
// The migrator contract. It has a lot of power. Can only be set through governance (owner).
IMigratorChef public migrator;
// 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.
uint256 public totalAllocPoint = 0;
// The block number when SUSHI mining starts.
uint256 public startBlock;
uint256 public startBlock;
uint256 public poolLength = 5; // DAI, USDC, USDT, BUSD, ESD
uint256 public constant BLOCKS_PER_WEEK = 46500;
uint256[] public epochTotalRewards = [200000 ether, 150000 ether, 100000 ether, 50000 ether];
// Block number when each epoch ends.
uint[4] public epochEndBlocks;
// Reward per block for each of 4 epochs (last item is equal to 0 - for sanity).
uint[5] public epochBsdPerBlock;
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);
constructor(
constructor(
SushiToken _sushi,
address _bsd,
address _devaddr,
uint256 _sushiPerBlock,
uint256 _startBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
address[] memory _lpTokens
) public {
) public {
sushi = _sushi;
require(block.number < _startBlock, "late");
devaddr = _devaddr;
if (_bsd != address(0)) bsd = IERC20(_bsd);
sushiPerBlock = _sushiPerBlock;
startBlock = _startBlock; // supposed to be 11,465,000 (Wed Dec 16 2020 15:00:00 UTC)
bonusEndBlock = _bonusEndBlock;
epochEndBlocks[0] = startBlock + BLOCKS_PER_WEEK;
startBlock = _startBlock;
uint256 i;
}
for (i = 1; i <= 3; ++i) {
epochEndBlocks[i] = epochEndBlocks[i - 1] + BLOCKS_PER_WEEK;
function poolLength() external view returns (uint256) {
return poolInfo.length;
}
// Add a new lp to the pool. Can only be called by the owner.
// XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
function add(uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
}
uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
for (i = 0; i <= 3; ++i) {
totalAllocPoint = totalAllocPoint.add(_allocPoint);
epochBsdPerBlock[i] = epochTotalRewards[i].div(BLOCKS_PER_WEEK);
poolInfo.push(PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardBlock: lastRewardBlock,
accSushiPerShare: 0
}));
}
// Update the given pool's SUSHI allocation point. Can only be called by the owner.
function set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
}
totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
epochBsdPerBlock[4] = 0;
poolInfo[_pid].allocPoint = _allocPoint;
if (_lpTokens.length == 0) {
}
_addPool(address(0x6B175474E89094C44Da98b954EedeAC495271d0F)); // DAI
_addPool(address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)); // USDC
// Set the migrator contract. Can only be called by the owner.
_addPool(address(0xdAC17F958D2ee523a2206206994597C13D831ec7)); // USDT
function setMigrator(IMigratorChef _migrator) public onlyOwner {
_addPool(address(0x4Fabb145d64652a948d72533023f6E7A623C7C53)); // BUSD
migrator = _migrator;
_addPool(address(0x36F3FD68E7325a35EB768F1AedaAe9EA0689d723)); // ESD
} else {
require(_lpTokens.length == poolLength, "Need exactly 5 lpToken address");
for (i = 0; i < poolLength; ++i) {
_addPool(_lpTokens[i]);
}
}
}
}
// Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
// Add a new lp to the pool. Called in the constructor only.
function migrate(uint256 _pid) public {
function _addPool(address _lpToken) internal {
require(address(migrator) != address(0), "migrate: no migrator");
require(_lpToken != address(0), "!_lpToken");
PoolInfo storage pool = poolInfo[_pid];
poolInfo.push(
IERC20 lpToken = pool.lpToken;
PoolInfo({
uint256 bal = lpToken.balanceOf(address(this));
lpToken : IERC20(_lpToken),
lpToken.safeApprove(address(migrator), bal);
lastRewardBlock : startBlock,
IERC20 newLpToken = migrator.migrate(lpToken);
accBsdPerShare : 0
require(bal == newLpToken.balanceOf(address(this)), "migrate: bad");
})
pool.lpToken = newLpToken;
);
}
}
// Return reward multiplier over the given _from to _to block.
// Return reward multiplier over the given _from to _to block.
function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
function getGeneratedReward(uint256 _from, uint256 _to) public view returns (uint256) {
if (_to <= bonusEndBlock) {
for (uint8 epochId = 4; epochId >= 1; --epochId) {
return _to.sub(_from).mul(BONUS_MULTIPLIER);
if (_to >= epochEndBlocks[epochId - 1]) {
} else if (_from >= bonusEndBlock) {
if (_from >= epochEndBlocks[epochId - 1]) return _to.sub(_from).mul(epochBsdPerBlock[epochId]);
return _to.sub(_from);
uint256 _generatedReward = _to.sub(epochEndBlocks[epochId - 1]).mul(epochBsdPerBlock[epochId]);
} else {
if (epochId == 1) return _generatedReward.add(epochEndBlocks[0].sub(_from).mul(epochBsdPerBlock[0]));
return bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(
for (epochId = epochId - 1; epochId >= 1; --epochId) {
_to.sub(bonusEndBlock)
if (_from >= epochEndBlocks[epochId - 1]) return _generatedReward.add(epochEndBlocks[epochId].sub(_from).mul(epochBsdPerBlock[epochId]));
);
_generatedReward = _generatedReward.add(epochEndBlocks[epochId].sub(epochEndBlocks[epochId - 1]).mul(epochBsdPerBlock[epochId]));
}
return _generatedReward.add(epochEndBlocks[0].sub(_from).mul(epochBsdPerBlock[0]));
}
}
}
return _to.sub(_from).mul(epochBsdPerBlock[0]);
}
}
// View function to see pending SUSHIs on frontend.
// View function to see pending BSDs on frontend.
function pendingSushi(uint256 _pid, address _user) external view returns (uint256) {
function pendingBasisDollar(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 accSushiPerShare = pool.accSushiPerShare;
uint256 accBsdPerShare = pool.accBsdPerShare;
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 _generatedReward = getGeneratedReward(pool.lastRewardBlock, block.number);
uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
accBsdPerShare = accBsdPerShare.add(_generatedReward.div(poolLength).mul(1e18).div(lpSupply));
accSushiPerShare = accSushiPerShare.add(sushiReward.mul(1e12).div(lpSupply));
}
return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);
}
// 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);
}
}
return user.amount.mul(accBsdPerShare).div(1e18).sub(user.rewardDebt);
}
}
// 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.number <= pool.lastRewardBlock) {
if (block.number <= pool.lastRewardBlock) {
return;
return;
}
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
if (lpSupply == 0) {
pool.lastRewardBlock = block.number;
pool.lastRewardBlock = block.number;
return;
return;
}
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 _generatedReward = getGeneratedReward(pool.lastRewardBlock, block.number);
uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
pool.accBsdPerShare = pool.accBsdPerShare.add(_generatedReward.div(poolLength).mul(1e18).div(lpSupply));
sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);
pool.accSushiPerShare = pool.accSushiPerShare.add(sushiReward.mul(1e12).div(lpSupply));
pool.lastRewardBlock = block.number;
pool.lastRewardBlock = block.number;
}
}
// Deposit LP tokens to MasterChef for SUSHI allocation.
// Deposit LP tokens.
function deposit(uint256 _pid, uint256 _amount) public {
function deposit(uint256 _pid, uint256 _amount) public {
address _sender = msg.sender;
PoolInfo storage pool = poolInfo[_pid];
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.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.accSushiPerShare).div(1e12).sub(user.rewardDebt);
uint256 _pending = user.amount.mul(pool.accBsdPerShare).div(1e18).sub(user.rewardDebt);
if(pending > 0) {
if (_pending > 0) {
safeSushiTransfer(msg.sender, pending);
safeBsdTransfer(_sender, _pending);
emit RewardPaid(_sender, _pending);
}
}
}
}
if(_amount > 0) {
if (_amount > 0) {
pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
pool.lpToken.safeTransferFrom(_sender, address(this), _amount);
user.amount = user.amount.add(_amount);
user.amount = user.amount.add(_amount);
}
}
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
user.rewardDebt = user.amount.mul(pool.accBsdPerShare).div(1e18);
emit Deposit(msg.sender, _pid, _amount);
emit Deposit(_sender, _pid, _amount);
}
}
// Withdraw LP tokens from MasterChef.
// Withdraw LP tokens.
function withdraw(uint256 _pid, uint256 _amount) public {
function withdraw(uint256 _pid, uint256 _amount) public {
address _sender = msg.sender;
PoolInfo storage pool = poolInfo[_pid];
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.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.accSushiPerShare).div(1e12).sub(user.rewardDebt);
uint256 _pending = user.amount.mul(pool.accBsdPerShare).div(1e18).sub(user.rewardDebt);
if(pending > 0) {
if (_pending > 0) {
safeSushiTransfer(msg.sender, pending);
safeBsdTransfer(_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.lpToken.safeTransfer(address(msg.sender), _amount);
pool.lpToken.safeTransfer(_sender, _amount);
}
}
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);
user.rewardDebt = user.amount.mul(pool.accBsdPerShare).div(1e18);
emit Withdraw(msg.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;
pool.lpToken.safeTransfer(address(msg.sender), user.amount);
emit EmergencyWithdraw(msg.sender, _pid, user.amount);
user.amount = 0;
user.amount = 0;
user.rewardDebt = 0;
user.rewardDebt = 0;
pool.lpToken.safeTransfer(address(msg.sender), amount);
emit EmergencyWithdraw(msg.sender, _pid, amount);
}
}
// Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
// Safe bsd transfer function, just in case if rounding error causes pool to not have enough BSDs.
function safeSushiTransfer(address _to, uint256 _amount) internal {
function safeBsdTransfer(address _to, uint256 _amount) internal {
uint256 sushiBal = sushi.balanceOf(address(this));
uint256 _bsdBal = bsd.balanceOf(address(this));
if (_amount > sushiBal) {
if (_bsdBal > 0) {
sushi.transfer(_to, sushiBal);
if (_amount > _bsdBal) {
} else {
bsd.transfer(_to, _bsdBal);
sushi.transfer(_to, _amount);
} else {
bsd.transfer(_to, _amount);
}
}
}
}
}
// Update dev address by the previous dev.
function setGovernance(address _governance) external {
function dev(address _devaddr) public {
require(msg.sender == governance, "!governance");
require(msg.sender == devaddr, "dev: wut?");
require(_governance != address(0), "zero");
devaddr = _devaddr;
governance = _governance;
}
function governanceRecoverUnsupported(IERC20 _token, uint256 amount, address to) external {
require(msg.sender == governance, "!governance");
if (block.number < epochEndBlocks[3] + BLOCKS_PER_WEEK * 12) {
// do not allow to drain lpToken if less than 3 months after farming
require(_token != bsd, "!bsd");
for (uint256 pid = 0; pid < poolLength; ++pid) {
PoolInfo storage pool = poolInfo[pid];
require(_token != pool.lpToken, "!pool.lpToken");
}
}
_token.safeTransfer(to, amount);
}
}
}
}