MasterChef interpretation of SushiSwap
1. Data structure of MasterChef
MasterChef is at the core of SushiSwap, through which users can conduct mobile mining. MasterChef contains two main data structures: UserInfo and PoolInfo
1.1,UserInfo
struct UserInfo { uint256 amount; // How many LP tokens the user has provided. uint256 rewardDebt; // Reward debt. See explanation below. // // We do some fancy math here. Basically, any point in time, the amount of SUSHIs // entitled to a user but is pending to be distributed is: // // pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt // // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: // 1. The pool's `accSushiPerShare` (and `lastRewardBlock`) gets updated. // 2. User receives the pending reward sent to his/her address. // 3. User's `amount` gets updated. // 4. User's `rewardDebt` gets updated. }
amount is the number of lptokens pledged by the user, and rewardDebt represents the number of rewards the user has received.
1.2,PoolInfo
struct PoolInfo { 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 SUSHIs distribution occurs. uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below. }
Lptoken is ERC20 standard token. The initial lptoken of SushiSwap is the liquidity of Uniswap. The liquidity generated after Uniswap pledge is actually the token of UniswapPair. SushiSwap can pledge the liquidity of Uniswap by setting the address of UniswapPair to pool. Later, SushiSwap completed a migration, and lptoken changed from the liquidity token of Uniswap to the liquidity token of SushiSwap.
allocPoint is the allocation proportion of pledge pool, and lastRewardBlock is the number of blocks allocated with rewards last time.
accSushiPerShare is the global income of pledging an LPToken. The user relies on this to calculate the actual income. The principle is very simple. When pledging an LPToken, the user will write down the current accSushiPerShare as the starting point. When the pledge is released, the user's actual income can be obtained by subtracting the starting point from the latest accSushiPerShare.
1.3 other data structures
SushiToken public sushi; // 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. PoolInfo[] public poolInfo; // Info of each user that stakes LP tokens. mapping(uint256 => mapping(address => UserInfo)) public userInfo; // Total allocation poitns. 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;
sushi is an ERC20 token, which is the token reward obtained by pledging liquidity.
devaddr is the developer's address, which is used to allocate the handling fee of sushi reward
bonusEndBlock. At first, sushi was pledged by the liquidity of Uniswap. In order to attract users, a bonus multiplier bonus was set_ Multiplier and a bonus deadline block, bonusEndBlock, the number of rewards obtained before bonusEndBlock will be multiplied by 10. After this block is migrated, there will be no double reward after migration, and the subsequent value will not be used.
sushiPerBlock, the number of sushi excavated in each block
As like as two peas, migrator, the migration tool class implements the principle of creating a SushiSwapPair identical to UniswapPair, then redeeming the transaction pair (such as USDT/DAI) with the UniswapPair liquidity of the user, then adding the transaction to SushiSwapPair, obtaining the SushiSwapPair mobile token, and finally pledge the SushiSwapPair's mobile token.
totalAllocPoint is the total number of points allocated
startBlock is the start block
2. Constructor
constructor( SushiToken _sushi, address _devaddr, uint256 _sushiPerBlock, uint256 _startBlock, uint256 _bonusEndBlock ) public { sushi = _sushi; devaddr = _devaddr; sushiPerBlock = _sushiPerBlock; bonusEndBlock = _bonusEndBlock; startBlock = _startBlock; }
MasterChef initializes the address of the incoming sushi token (value: 0x6b3595068778dd592e39a122f4f5a5cf09c90fe2 ), developer address (value: 0xe94b5eec1fa96ceecbd33ef5baa8d00e4493f4f3 , the number of sushi allocated to each block (value is) 100 *1e18), reward end block (value 10850000) and start block (value 10750000)
3. Add pool pledge
function add( uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate ) public onlyOwner { if (_withUpdate) { massUpdatePools(); } uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock; totalAllocPoint = totalAllocPoint.add(_allocPoint); poolInfo.push( PoolInfo({ lpToken: _lpToken, allocPoint: _allocPoint, lastRewardBlock: lastRewardBlock, accSushiPerShare: 0 }) ); }
The code is very simple. Generate a poolInfo, add it to the array, and then update totalAllocPoint, where_ allocPoint refers to the proportion of mining in this pool. For example, totalAllocPoint is 10000_ allocPoint is 100, and each block digs a total of 100, so the pool allocated to each block is 100 * (100 / 10000) = 1
This method can only be executed by the administrator because there is an onlyOwner modifier
4. Modify pledge pool parameters
function set( uint256 _pid, uint256 _allocPoint, bool _withUpdate ) public onlyOwner { if (_withUpdate) { massUpdatePools(); } totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add( _allocPoint ); poolInfo[_pid].allocPoint = _allocPoint; }
At present, only the allocation proportion of pledged mining can be modified, and only administrators can implement it.
5. Perform migration
function setMigrator(IMigratorChef _migrator) public onlyOwner { migrator = _migrator; } function migrate(uint256 _pid) public { require(address(migrator) != address( 0), "migrate: no migrator"); PoolInfo storage pool = poolInfo[_pid]; IERC20 lpToken = pool.lpToken; uint256 bal = lpToken.balanceOf(address(this)); lpToken.safeApprove(address(migrator), bal); IERC20 newLpToken = migrator.migrate(lpToken); require(bal == newLpToken.balanceOf(address(this)), "migrate: bad"); pool.lpToken = newLpToken; }
The administrator will first set the migrator and then migrate for a single pledge pool. The migration process first authorizes the migrator (safeApprove), and then is controlled by the migrator. The migrator will return a new LPToken, and then reset the pledge pool. Let's see how sushi's migrator operates:
function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) { require(msg.sender == chef, "not from master chef"); require(block.number >= notBeforeBlock, "too early to migrate"); require(orig.factory() == oldFactory, "not from old factory"); address token0 = orig.token0(); address token1 = orig.token1(); IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); if (pair == IUniswapV2Pair(address( 0))) { pair = IUniswapV2Pair(factory.createPair(token0, token1)); } uint256 lp = orig.balanceOf(msg.sender); if (lp == 0) return pair; desiredLiquidity = lp; //User mobility is also supported by Uniswap orig.transferFrom(msg.sender, address(orig), lp); //Uniswap gives the pledged token transaction pair to the pair of sushiswap orig.burn(address(pair)); //sushiswap issues liquidity to users pair.mint(msg.sender); desiredLiquidity = uint256( -1); return pair; }
As mentioned above, sushiswap initially relies on the liquidity of uniswap, so the lpToken above is actually UniswapPair. Then, get the two tokens in the specific transaction pair through UniswapPair, and then create sushiswappair in sushi (both implementation classes of IUniswapV2Pair interface), Then redemption of user's liquidity in Uniswap (first transfer to Uniswap, then call burn, note that the object of burn here is pair, so Uniswap will return the two pledged token to SushiSwapPair's address), and finally call Mint mint to give users the additional mobility of the SushiSwapPair, thus completing the migration of user mobility.
6. Update pledge pool income
function updatePool(uint256 _pid) public { PoolInfo storage pool = poolInfo[_pid]; if (block.number <= pool.lastRewardBlock) { return; } uint256 lpSupply = pool.lpToken.balanceOf(address(this)); if (lpSupply == 0) { pool.lastRewardBlock = block.number; return; } uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number); uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div( totalAllocPoint ); 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; }
First, calculate the number of lptokens in the pledge pool. If it is 0, only lastRewardBlock will be updated. Otherwise, a multiplier multiplier will be calculated first
// Return reward multiplier over the given _from to _to block. function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) { if (_to <= bonusEndBlock) { return _to.sub(_from).mul(BONUS_MULTIPLIER); } else if (_from >= bonusEndBlock) { return _to.sub(_from); } else { return bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add( _to.sub(bonusEndBlock) ); } }
The calculation here is to be compatible with bonusEndBlock. If to is less than bonusEndBlock, it means that the pledge pool is completely in the reward mining stage and will be multiplied by a multiple of BONUS_MULTIPLIER, if from is greater than bonusEndBlock, it means that the pledge pool has not participated in the reward mining at all, so it is OK to simply use to-from. The last else is to deal with the pledge pool, part of which participates in the reward mining, and part of which is the conventional mining after the end.
The calculation of multiplier is the number of reward blocks from lastRewardBlock to the current block. After obtaining the multiplier, calculate the sushi reward for this period of time
uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div( totalAllocPoint );
multiplier*sushiPerBlock is the total sushi reward, pool Allocpoint / totalallocpoint is the allocation proportion of the current pledge pool.
Next, 10% sushiReward is allocated to the developer address as the handling fee, and then the total sushiReward is allocated to the current pledge pool.
Then calculate accSushiPerShare to accumulate.
Finally, update lastRewardBlock.
7. View user pledge income
function pendingSushi(uint256 _pid, address _user) external view returns (uint256) { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][_user]; uint256 accSushiPerShare = pool.accSushiPerShare; uint256 lpSupply = pool.lpToken.balanceOf(address(this)); if (block.number > pool.lastRewardBlock && lpSupply != 0) { uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number); uint256 sushiReward = multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div( totalAllocPoint ); accSushiPerShare = accSushiPerShare.add( sushiReward.mul( 1e12).div(lpSupply) ); } return user.amount.mul(accSushiPerShare).div( 1e12).sub(user.rewardDebt); }
All the previous logics are updating the latest income of the current pledge pool. The logic is similar to that of updatePool, but does not execute mint, but only performs logical calculation. In the last line, multiply the amount pledged by the user by accSushiPerShare to obtain the total number of sushi obtained by the user in theory, and then subtract the number of sushi actually obtained by the user, rewardDebt, which is the remaining data that has not been obtained.
8. Users pledge LPToken for mining
function deposit(uint256 _pid, uint256 _amount) public { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][msg.sender]; updatePool(_pid); if (user.amount > 0) { uint256 pending = user.amount.mul(pool.accSushiPerShare).div( 1e12).sub( user.rewardDebt ); safeSushiTransfer(msg.sender, pending); } pool.lpToken.safeTransferFrom( address(msg.sender), address(this), _amount ); user.amount = user.amount.add(_amount); user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div( 1e12); emit Deposit(msg.sender, _pid, _amount); }
First update the pledge pool income, then calculate the sushi income not obtained by the user (if the user has pledged before), and transfer these income to the user account. Then transfer the user's LPToken to the pledge pool, and finally update the number of lptokens pledged by the user, and set the latest amount*accSushiPerShare to rewardDebt. This step is actually to set a starting point for user reward, which is exactly the starting point for the above calculation of pendingSushi.
9. Release pledge
function withdraw(uint256 _pid, uint256 _amount) public { PoolInfo storage pool = poolInfo[_pid]; UserInfo storage user = userInfo[_pid][msg.sender]; require(user.amount >= _amount, "withdraw: not good"); updatePool(_pid); uint256 pending = user.amount.mul(pool.accSushiPerShare).div( 1e12).sub( user.rewardDebt ); safeSushiTransfer(msg.sender, pending); user.amount = user.amount.sub(_amount); user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div( 1e12); pool.lpToken.safeTransfer(address(msg.sender), _amount); emit Withdraw(msg.sender, _pid, _amount); }
First update the pledge pool income, then calculate the sushi income not obtained by the user, transfer these income to the user account, then update the rewardDebt, and finally return the LPToken to the user.
</article>