Analysis of the billion-dollar algorithm — SushiSwap’s MasterChef smart contract

Eszymi
Coinmonks
8 min readFeb 2, 2023

--

In this post, I would like to scrutinize one of the SushiSwap’s core smart contract. Especially, I would like to show a mechanism of the reward that is implemented there. This mechanism is so popular, that Paradigm call it as The billion-dollar algorithm, therefor I think it would be nice to understand how it works. The analyzed contract is the first version of the MasterChef contract. I choose it to avoid more difficult stuff and focus on the main idea.

Let’s introduce the actors

The contract starts by define two structures

struct UserInfo {
uint256 amount;
uint256 rewardDebt;
}

struct PoolInfo {
IERC20 lpToken;
uint256 allocPoint;
uint256 lastRewardBlock;
uint256 accSushiPerShare;
}

The first one, UserInfo, will contain information about two pieces of information:

  • amount — how many Liquid Provider (LP) tokens the user has supplied
  • rewardDebt — this one is less intuitive, thus I explain it lately

In the second one, PoolInfo, we have 4 different variables:

  • lpToken — its a token linked to the pool
  • allocPoint — number use to set importance of this pool. Lately I will show what I mean.
  • lastRewardBlock — number of block, when the reward in the pool was the last time calculated
  • accSushiPerShare — how much sushi we get by one LP token we deposited in the pool. This value is multiply by 10¹². Why that? We see lately.

Both of these structure contain crucial information about user and pool, and they are the main bricks of the whole contract.

Now, when we defined the structure, we have to define the variables


SushiToken public sushi; // SUSHI token

address public devaddr; // address of the developer

uint256 public bonusEndBlock; // Block number when bonus SUSHI period ends.

uint256 public sushiPerBlock; // SUSHI tokens created per block.

uint256 public constant BONUS_MULTIPLIER = 10; // Bonus muliplier for early sushi makers.

IMigratorChef public migrator;

PoolInfo[] public poolInfo; // Info of each pool.

mapping(uint256 => mapping(address => UserInfo)) public userInfo; // Info of each user that stakes LP tokens.

uint256 public totalAllocPoint = 0; // Total allocation poitns. Must be the sum of all allocation points in all pools.

uint256 public startBlock; // The block number when SUSHI mining starts.

When we analyze the variables, we see a few interesting things. The contract has a bonus period defined by the startBlock, and bonusEndBlock. In this period, the reward is 10 times bigger, as we can see in the BONUS_MULTIPLIER. Because BONUS_MULTIPLIER is a constant variable, we could note the multiplier level is fixed. Also, we see that how much sushi will be minted in every block is defined. Additionally, notice how the contract uses previously defined structures.

Through all defined variables, there is a mitigator. This is a contract that perform LP token migration from legacy UniswapV2 to SushiSwap. I won’t focus on it yet, because it’s more complex stuff. But I want to tell more about it in one of my future posts.

The last one, totalAllocPoint. The concept of this one is easy, but for now I keep it in the secret that I reveal in the nearest future.

The fun has begun

Firstly, we need a constructor. It’s a self-explanatory part of the contract, but extremely necessary.

constructor(
SushiToken _sushi,
address _devaddr,
uint256 _sushiPerBlock,
uint256 _startBlock,
uint256 _bonusEndBlock
) public {
sushi = _sushi;
devaddr = _devaddr;
sushiPerBlock = _sushiPerBlock;
bonusEndBlock = _bonusEndBlock;
startBlock = _startBlock;
}

Now we can play with functions.

add( uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate ) public onlyOwner

This is a powerful function, therefor only the owner can use it. The main part of this is creating a new pool.

totalAllocPoint = totalAllocPoint.add(_allocPoint);
poolInfo.push(
PoolInfo({
lpToken: _lpToken,
allocPoint: _allocPoint,
lastRewardBlock: lastRewardBlock,
accSushiPerShare: 0
})
);

Here we see two interesting things:

  • the given value of _allocPoint is set as allocPoint, and also this value is added to the totalAllocPoint
  • accSushiPerShare is equal to 0.

It’s important to note that there has to be only one pool for every LP token. In the other situation, it could be bad.

set( uint256 _pid, uint256 _allocPoint, bool _withUpdate ) public onlyOwner

Again, function only for the owner. This one is responsible for changing the allocPoint in the selected pool. We see also, it removes old value from the totalAllocPoint and adds the new.

totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
_allocPoint
);
poolInfo[_pid].allocPoint = _allocPoint;

So now, we know totalAllocPoint is a sum up of all created pool.

getMultiplier(uint256 _from, uint256 _to) public view returns (uint256)

Supplementary function that help use the bonus counting period in the smooth way.

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)
);
}

We see the if-statement with three possibilities. Each of them is related with this, where the parameters _to and _from are on this arrow.

If minimum one of them is before bonusEndBlock, then a part or whole distance between them is multiply by the BONUS_MULTIPLIER. And as we know, it is equal 10. So we can think about it, like time distance is 10 times bigger than it really is.

updatePool(uint256 _pid) public

Here everything starts making more interesting. On the very beginning, we choose the pool and checking if it was lastly updated and if the pool contains any tokens.

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;
}

Then it uses our familiar getMultiplier function and calculates the SushiReward. The amount of blocks saved as multiplier is multiplying with amount of sushi created in time of one block. Then it is time by pool.allocPoint divided by totalAllocPoint.

uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);

Now we see the true purpose of using this allocative points. We can write totalAllocPoint as

so this statement is always true

Therefor

and

So what information do we get from this? During one block, there is sushiPerBlock SUSHIs set as a reward. When the block is before the bonusEndBlock it is worth 10 blocks. SUSHIs from this block is divided by every pool. The ratio of poll.allocPoint to totalAllocPoint say how big part of the whole branch of SUSHIs go to this pool.

In the next step, the contract mint calculated sushiReward and give it to itself. Also, it mints 0.1 of sushiReward and grant it to the developer’s address.

sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);

At the end of this function, to the value of old accSushiPerShare is added a sushiReward times 10¹² and divided by the amount of LP tokens on the contract. We use here 10¹² to avoid working with decimals, because Solidity doesn’t catch that.

The finish result is saved as accSushiPerShare, and it contains information how much SUSHIs we get for every one LP token we have in the pool.

The last line, just update information when reward was lastly calculated.

pool.accSushiPerShare = pool.accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
pool.lastRewardBlock = block.number;

pendingSushi(uint256 _pid, address _user) external view returns (uint256)

The job of this function is showing the user how much SUSHIs is pending. What does it mean? It’s simple. It informs how much sushi the user get just now. The method to calculate is the same as in the previous function, but we can find one difference. In the last line

return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt);

We multiply user’s amount by quantity of SUSHIs per one LP token. Then we have to divide by 10¹², because previously we multiplied it by that. And at the end, we subtract rewardDebt. What is this rewardDebt? It’s kind of offset, that guard we don’t take the reward twice or take reward that doesn’t belong to us. In next to functions we will see, how it’s calculating.

deposit(uint256 _pid, uint256 _amount) public

As we can guess by the name, this function gives us the ability to deposit our LP tokens to the chosen pool. Firstly, it checks if user had deposited any LP tokens to the pool. If the answer is yes, then the reward is calculating and SUSHIs aretransfer to the user.

if (user.amount > 0) {
uint256 pending =
user.amount.mul(pool.accSushiPerShare).div(1e12).sub(
user.rewardDebt
);
safeSushiTransfer(msg.sender, pending);
}

In other situation, the function just skips this snippet of code and go further, exactly to the point where deposited amount and the rewardDebt are updated. As we see, the rewardDebt will be equal to the new amount times accSushiPerShare, because you take all SUSHI you can and now any SUSHI is for you. If you would like more, you have to wait.

user.amount = user.amount.add(_amount);
user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12);

withdraw(uint256 _pid, uint256 _amount) public

Again, the name of the function says everything. And except sending back you your LP tokens, this is very similar to the deposit function.

There are two other simple functions implemented in this contract:

  • emergencyWithdraw(uint256 _pid) public — Withdraw without caring about rewards. EMERGENCY ONLY
  • safeSushiTransfer(address _to, uint256 _amount) internal Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.

Summary

MasterChef is a relatively simple smart contract that contains a powerful algorithm to rewards users who staking tokens on it. And even now, when SushiSwap has developed this contract to MasterChefV2 and add a lot of new features, that what I showed here is still a foundation.

MasterChef is a relatively simple smart contract that contains a powerful algorithm to rewards users who staking tokens on it. And even now, when SushiSwap has developed this contract to MasterChefV2 and add a lot of new features, that what I showed here is still a foundation.

I hope you find this post useful. If you have any idea, how could I make my posts better, let my know. I am always ready to learn. You can connect with me on LinkedIn and Telegram.

If you would like to talk with me about this or any other topic I wrote, feel free. I’m open to conversation.

Happy learning!

New to trading? Try crypto trading bots or copy trading on best crypto exchanges

--

--