sushiswap之添加流性挖矿解读

参考文章:

参考文章

MasterChef 源码

// SPDX-License-Identifier: MIT

pragma solidity 0.6.12;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/utils/EnumerableSet.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./SushiToken.sol";

interface IMigratorChef {
    // 执行从旧 UniswapV2 到 SushiSwap 的 lpToken 迁移。 获取当前 lpToken 地址并返回新的 lpToken 地址。
    // 迁移器应该对调用者的 lpToken 具有完全访问权限。返回新的lpToken 地址。
    // !!! 迁移器必须具有对 UniswapV2 lpToken 的允许访问权限。
    // sushishap 必须铸造完全相同数量的 SushiSwap lpToken,否则会发生一些不好的事情。传统的 UniswapV2 不这样做,所以要小心
    function migrate(IERC20 token) external returns (IERC20);
}

// MasterChef 是 sushi 的所有者。他会铸造 suhsi,是个公平的合约。
//
// Note 它是自己的,拥有者拥有巨大的权力。一旦 SUSHI 被充分分配,并且社区可以展示自己来治理自己,所有权将被转移到治理智能合约中。
// 好好读吧。希望它没有错误。上帝保佑。
contract MasterChef is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // 每个用户的信息。
    struct UserInfo {
        uint256 amount;     // 用户提供的 lpToken 数量
        uint256 rewardDebt; // 见下文解释。

        // 我们在这里做一些奇特的数学运算。基本上,在任何时间点,用户有权获得但等待分配的用户数量为:
        //   pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt
        //
        //  每当用户将LP令牌存入或提取到池中时。事情是这样的:
        //   1. 池的 “accSushiPerShare ”(和“lastRewardBlock ”) 会更新。
        //   2. 用户将收到发送到其地址的待定奖励。
        //   3. 用户的 “amount” 得到更新。
        //   4. 用户的 “rewardDebt” 得到更新。
    }
    // 每个抵押的池子的信息。
    struct PoolInfo {
        IERC20 lpToken; // LP令牌合同的地址。
        uint256 allocPoint; // 分配给该池的分配点数。使用它按块分发。
        uint256 lastRewardBlock; // 支持此分布发生的最后一个块号。
        uint256 accSushiPerShare; // 累计每股收益,乘以1e12。见下文
    }

    // sushi 币的地址
    SushiToken public sushi;
    // 项目方开发者地址
    address public devaddr;

    // 奖励SUSHI期结束时的区块号。
    uint256 public bonusEndBlock;
    // 每个区块创建的SUSHI代币。
    uint256 public sushiPerBlock;

    // 早期寿司制造商的奖金倍增器。
    uint256 public constant BONUS_MULTIPLIER = 10;
    // 移民合同。它有很大的作用。只能通过治理(所有者)来设置。
    IMigratorChef public migrator;

    // 每个池的信息。
    PoolInfo[] public poolInfo;
    // 持有LP令牌的每个用户的信息。
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;

    // 总分配点。必须是所有池中所有分配点的总和。
    uint256 public totalAllocPoint = 0;
    // suhis 开采开始时的区块编号。
    uint256 public startBlock;

    // 分别为 抵押、提取、紧急情况提取 3 个事件
    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
    );

    // 部署时需要初始化的参数设置
    constructor(
        SushiToken _sushi,
        address _devaddr,
        uint256 _sushiPerBlock,
        uint256 _startBlock,
        uint256 _bonusEndBlock
    ) public {
        sushi = _sushi;
        devaddr = _devaddr;
        sushiPerBlock = _sushiPerBlock;
        bonusEndBlock = _bonusEndBlock;
        startBlock = _startBlock;
    }

    // 获取 pool 的总个数
    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    // 向池中添加新的 lpToken。只能由 onlyOwner 调用(项目方添加新的一个 lpToken 抵押池,既创建了一个新的对币池子)
    // !!!! onlyOwner 不要多次添加同一个 lpToken。如果你这样做,奖励就会变得一团糟。
    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
            })
        );
    }

    // 更新设置给定池的 SUSHI 分配点。只能由 onlyOwner 调用
    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;
    }

    // 设置迁移器合约。只能由 onlyOwner 调用。
    function setMigrator(IMigratorChef _migrator) public onlyOwner {
        migrator = _migrator;
    }

    // 将 lpToken 进行迁移。任何人都可以调用(将 uniswap 的 lpToken 拿到 sushiswap 进行抵押流动性挖矿)
    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;
    }

    // 返回给定 from 到 to 的区块的奖励数量。
    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)
                );
        }
    }

    // 查看 user 地址在 pid 这个池子的 sushi 收益
    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);
    }

    // 更新所有池的奖励分配情况。注意 gas 消耗
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // 更新给定 pid 对应的池子的奖励分配,使其保持最新为最新的分配数据。
    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;
    }

    // 添加流动性后将得到的 lpToken 抵押到 MasterChef 合约进行流动性挖矿赚取 sushi 收益币。
    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);
    }

    // 将参与流动性挖矿的 lpToken 提取出来,并将挖矿的全部收益提取出来
    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);
    }

    // 不在乎奖励提取全部流动性挖矿的 lpToken。仅限紧急情况。
    function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        pool.lpToken.safeTransfer(address(msg.sender), user.amount);
        emit EmergencyWithdraw(msg.sender, _pid, user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
    }

    // 安全的sushi传递函数,以防舍入误差导致池中没有足够的sushi。
    function safeSushiTransfer(address _to, uint256 _amount) internal {
        // 查询当前合约地址 sushi 代币的数量
        uint256 sushiBal = sushi.balanceOf(address(this));
        // 如果 to 地址的 sushi 传入数量大于当前合约的总量,则将合约的总量发到 to 地址,反之则发送传入的 sushi 数量
        if (_amount > sushiBal) {
            sushi.transfer(_to, sushiBal);
        } else {
            sushi.transfer(_to, _amount);
        }
    }

    // 通过上一个开发者地址来设置的新开发者地址
    function dev(address _devaddr) public {
        require(msg.sender == devaddr, "dev: wut?");
        devaddr = _devaddr;
    }
}



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雲小妖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值