MasterChef (Sushiswap) and StablesPool

Created Diff never expires
180 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
264 lines
152 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
240 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);
}
}
}
}