Developers Docs
Search
⌃K

Understanding Smart Contracts

Understanding MasterChef

Before starting the subgraph development we need to understand how smart contract(s) implementation of the protocol works - at least the parts which impact the state of the market and individual user positions.

Find the core contract

Most often protocols consist of multiple smart contracts cooperating together to achieve desired functionality. In case of MasterChef it is a single contract taking care of farming! Well, almost - in May 2021 MasterChef V2 was deployed with some additional features. Currently both MasterChef implementations are live and being used, and in this walk-through we focus on the original MasterChef implementation. It is deployed here.

Dive into the code

Let's check the code! First thing to look for - how does contract keep track of existing farms and user investments? Is it like the approach used by Curve gauges (gauge is equivalent to Sushi farm) where each gauge is a smart contract deployed by main factory contract? Actually no, in case of MasterChef the farm and user info is stored in data structures within the single main contract:
// Info of each pool.
PoolInfo[] public poolInfo;
// Info of each user that stakes LP tokens.
mapping (uint256 => mapping (address => UserInfo)) public userInfo;
Terms farm and pool are used interchangeably and mean the same thing in the context of MasterChef
Alright, we can see there's an array of farms, and then index within that array is key in userInfo mapping. Value of the mapping is another mapping with user's address as a key and UserInfo struct as a value. Let's see what info these structs are made of.
// Info of each pool.
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.
}
PoolInfo contains address of the LP token and set of variables which determine how Sushi rewards are distributed.
// Info of each user.
struct UserInfo {
uint256 amount; // How many LP tokens the user has provided.
uint256 rewardDebt; // Reward debt. See explanation below.
}
UserInfo tracks how many SLP tokens user has staked into the farm and how many Sushi reward tokens he is entitled to. In our subgraph we will define entity schema which will store the same info as in the contract, so that at any point of time we will know state of the farms and user positions.

Functions

Now let's check in the code which functions update the state of those data structures. For poolInfo we find the following:
  • add(uint256 _allocPoint, IERC20 _lpToken, bool _withUpdate) - addition of a new farm
  • set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) - update farm's allocation point
  • migrate(uint256 _pid) - replace the LP token of a farm
  • updatePool(uint256 _pid) - update reward variables of a farm
Functions which update user's position in specific farm are:
  • deposit(uint256 _pid, uint256 _amount) - user deposits his SLP tokens into a farm
  • withdraw(uint256 _pid, uint256 _amount) - user withdraws his SLP tokens from a farm
  • emergencyWithdraw(uint256 _pid) - user withdraws his SLP tokens without getting rewards

Events

Now we know what are the main points of interaction with MasterChef and how its state can be modified. But how we collect that info? The main way subgraph collects info is by indexing events emitted by contract. Is there an event emitted by all of the functions which modify the state? Unfortunately, no.
event Deposit(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);
There are only 3 events defined within contract and they are emitted when user's position is updated. Ideally every instance of state modification should emit an event, but that's not case here (and in many other smart contracts out there). So how can we know that i.e. a new farm has been added? We will subscribe to function calls using call handlers. Using call handlers is not perfect solution and it brings its own set of challenges, more on that in later chapters.
Now we have all the prerequisites in place to start developing the subgraph!