..
\[\text{sushiPerBlock} = \text{sushiPerBlock} \times \frac{\text{Pool's allocPoint}}{\text{totalAllocPoint}}\]
\[\text{sushiReward} = \text{blocks} \times \text{sushiPerBlock} \times \frac{\text{Pool's allocPoint}}{\text{totalAllocPoint}}\]
\[\text{accSushiPerShare} += \frac{ \text{sushiReward} \times ACC\_SUSHI\_PRECISION}{ lpSupply}\]
\[\text{pending} = \left( \text{user.amount} \times \frac{accSushiPerShare}{ACC\_SUSHI\_PRECISION} \right) - \text{user.rewardDebt}\]
Sushiswap Masterchef
简介
- 介绍:SushiSwap的MasterChef是一个质押协议,一种流动性挖矿(Liquidity Mining)机制,也常被称为收益耕作(Yield Farming)
- V1版本铸造奖励的SuShi代币,V2的奖励来自V1版本。V2 通过质押 Dummy Token 到 V1 的特定池,间接获取 SUSHI 奖励,避免重复造币。我们在分析的时候简化其经济模型,尽量不分开V1与V2,看成整体。
- 模型:用户可以存入不同的代币(协议中是LP)到不同的池子,然后在每一个block中获取奖励(协议中是Sushi Token),管理者可以控制不同池子瓜分奖励的百分比。这是一个非常经典的模型
- 核心公式:
例子
假设当前 SushiSwap 的 MasterChef 配置如下:
sushiPerBlock
= 100 SUSHI(每个区块产出 100 SUSHI)。- 有 3 个质押池:
- 池子 0(ETH/USDT):
allocPoint
= 400,总质押量 1000 LP。 - 池子 1(SUSHI/ETH):
allocPoint
= 300,总质押量 500 LP。 - 池子 2(DAI/ETH):
allocPoint
= 200,总质押量 200 LP。
- 池子 0(ETH/USDT):
totalAllocPoint
= 400 + 300 + 200 = 900。
第一步:计算每个池子的奖励
每个区块的 SUSHI 奖励按 allocPoint
比例分配:
- 池子 0 奖励 = 100 × (400 / 900) ≈ 44.44 SUSHI/区块。
- 池子 1 奖励 = 100 × (300 / 900) ≈ 33.33 SUSHI/区块。
- 池子 2 奖励 = 100 × (200 / 900) ≈ 22.22 SUSHI/区块。
第二步:用户质押与奖励计算
假设用户 A 在池子 0(ETH/USDT)质押了 100 LP 代币,持续 10 个区块。
- 池子 0 每区块奖励 44.44 SUSHI,总质押量 1000 LP。
- 每 LP 的奖励(
accSushiPerShare
增量)= 44.44 / 1000 = 0.04444 SUSHI/区块。 - 10 个区块后,
accSushiPerShare
增加 = 0.04444 × 10 = 0.4444 SUSHI/LP(实际乘以 1e12 精度)。 - 用户 A 的奖励 = 100 × 0.4444 = 44.44 SUSHI。
第三步:用户提取
用户 A 调用 withdraw,提取 100 LP 代币,同时领取 44.44 SUSHI 奖励。其 rewardDebt
更新为 100 × 0.4444 = 44.44,确保不会重复领取。
多池子影响
如果管理员将池子 1 的 allocPoint
调整为 500(totalAllocPoint
变为 1100),则:
- 池子 0 奖励变为 100 × (400 / 1100) ≈ 36.36 SUSHI/区块。
- 池子 1 奖励变为 100 × (500 / 1100) ≈ 45.45 SUSHI/区块。
- 池子 2 奖励变为 100 × (200 / 1100) ≈ 18.18 SUSHI/区块。 这说明多池子之间通过
allocPoint
竞争奖励,管理员可以灵活调整激励方向。
数学原理
- 每区块SUSHI奖励(sushiPerBlock):
- 每个池子都有一个份额,每个区块有奖励数额,各个池子根据自己的份额占比瓜分这个奖励数额
function sushiPerBlock() public view returns (uint256 amount) {
amount = uint256(MASTERCHEF_SUSHI_PER_BLOCK)
.mul(MASTER_CHEF.poolInfo(MASTER_PID).allocPoint) / MASTER_CHEF.totalAllocPoint();
}
- 池的奖励累积(accSushiPerShare):
- 每个池根据其allocPoint占比获得SUSHI奖励。奖励按LP代币总量平摊,更新accSushiPerShare,表示每单位LP代币的累积奖励。
- 这里的pid是每个池子的编号
- 这里更新池子的行为,就是更新奖励的“累积量”,后续领取的时候,用”累积量“减去之前的领取时的”债务“,就是可领取的值。这种模式很常见。
function updatePool(uint256 pid) public returns (PoolInfo memory pool) {
pool = poolInfo[pid];
if (block.number > pool.lastRewardBlock) {
uint256 lpSupply = lpToken[pid].balanceOf(address(this));
if (lpSupply > 0) {
uint256 blocks = block.number.sub(pool.lastRewardBlock);
uint256 sushiReward = blocks.mul(sushiPerBlock()).mul(pool.allocPoint) / totalAllocPoint;
pool.accSushiPerShare = pool.accSushiPerShare.add((sushiReward.mul(ACC_SUSHI_PRECISION) / lpSupply).to128());
}
pool.lastRewardBlock = block.number.to64();
poolInfo[pid] = pool;
}
}
- 用户未领取的奖励(pendingSushi):
- 基于其质押的LP代币数量和池的accSushiPerShare,通过“奖励债务”机制计算。这个是一个查看函数。
- 核心:当前累积的奖励减去之前领取时的债务,差值就是可领取的数量
function pendingSushi(uint256 _pid, address _user) external view returns (uint256 pending) {
PoolInfo memory pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][_user];
uint256 accSushiPerShare = pool.accSushiPerShare;
uint256 lpSupply = lpToken[_pid].balanceOf(address(this));
if (block.number > pool.lastRewardBlock && lpSupply != 0) {
uint256 blocks = block.number.sub(pool.lastRewardBlock);
uint256 sushiReward = blocks.mul(sushiPerBlock()).mul(pool.allocPoint) / totalAllocPoint;
vouaccSushiPerShare = accSushiPerShare.add(sushiReward.mul(ACC_SUSHI_PRECISION) / lpSupply);
}
pending = int256(user.amount.mul(accSushiPerShare) / ACC_SUSHI_PRECISION).sub(user.rewardDebt).toUInt256();
}
每次用户存款或提取时,更新rewardDebt(债务):
\[\text{rewardDebt} += \text{amount} \times \frac{\text{accSushiPerShare}}{ACC\_SUSHI\_PRECISION}\]收获奖励时,rewardDebt设置为当前累积奖励,确保用户只领取新产生的奖励。
其他
管理
-
add():添加新的质押池
-
set():调整质押池的分配比例和奖励分配合约
用户
-
deposit():用户质押,函数执行之前会更新池子的状态
-
withdraw():用户取回资产,函数执行之前会更新池子的状态
-
harvest():用户收获已经产生的代币奖励,函数执行之前会更新池子的状态