SUSHI的源码及方案解析一(流动性挖矿部分的解析与克隆)

我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。本文首先对流动性挖矿(SUSHI)进行方案的解析,进而解决token是怎么来的问题。

首先说一下。我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。

前言

我的思路是这样的,如果把设备按功能分成不同的群体,那么群体内设备与设备之间通过token来完成信息的交互,这里最简单的例子就是基于预言机来完成信息的报价和购买,我们在《关于预言机nest的源码解析及克隆》里面已经对nest预言机部分的源码和克隆进行了解析,有兴趣的可以看一下。 接下来还有两个非常重要的内容需要解决。第一个是token是怎么来的,另一个是token之间是如何进行交互的。关于token是怎么来的,我们考虑的是模仿SUSHI的流动性挖矿来解决,而token之间是如何交易的,则模仿uniswap来完成。 本文首先对流动性挖矿(SUSHI)进行方案的解析。 特别说明,本文基于的是SUSHI早期的源码,那个时候交易所部分还没出来。

流动性挖矿(SUSHI)概述

流动性挖矿(SUSHI)很多人都知道开发者一个月赚了几千万的事情。我简单的从技术角度说一下。 SUSHI模仿YAM进行流动性挖矿,但创造性的用uniswap的令牌来完成。 主要的功能分四部分: 1.管理员建立uniswap令牌池 2.用户将自己的uniswap令牌(LP)存入令牌池 3.根据不同的令牌池权重和不同数量的用户LP存入币实时计算该用户应该获得的SUSHI 4.用户通过取功能获得SUSHI,以及存入的uniswap令牌。 5.将用户的LP令牌转移到SUSHI的去中心化交易所。 所以说核心函数其实就那么几个: 1.SUSHI的挖矿接口函数; 2.建立令牌池及修改令牌池(管理员权限) 3.用户存、取LP; 4.用户查看及取SUSHI; 5.将用户的LP令牌转移到SUSHI的去中心化交易所

Token.sol

这个里面erc20部分大部分内容来自于openzeppelin,而// SushiToken with Governance.部分主要来自于yam。 大家理解为一个简单的可以挖矿的erc20币即可。 这里面的关键代码就一句:

contract PITAYAToken is ERC20("PITAYAswap", "PITAYA"), Ownable {

这里的PITAYAToken是我自己改的名字。后面的PITAYAswap是项目的名字,PITAYA是货币符号。 也就是说,用这段代码克隆的同学,就改这三个地方即可。

chef.sol

这个里面没有什么要改的,但要理解。 这个里面看似合约很多,但其实主要关注的只有一个,MasterChef。 我们对照着前言里面提到的几个函数说明一下。

contract MasterChef is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // Info of each user.
    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
    }

    // Info of each pool.
//每一个矿池的数据结构
    struct PoolInfo {
        IERC20 lpToken;           // Address of LP token contract.uniswap的令牌地址
        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.sushi的配额
    }

    // The SUSHI TOKEN!
//sushi的token地址
    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 = 2;
    // The migrator contract. It has a lot of power. Can only be set through governance (owner).
//迁移地址,当sushi的去中心化交易所完成后进行的迁移
    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;

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

    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    // Add a new lp to the pool. Can only be called by the owner.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
//增加矿池,管理员权限
    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
        }));
    }

    // Update the given pool's SUSHI allocation point. Can only be called by the owner.
//设置矿池参数,管理员权限
    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;
    }

    // Set the migrator contract. Can only be called by the owner.
//设置迁移地址,管理员权限
    function setMigrator(IMigratorChef _migrator) public onlyOwner {
        migrator = _migrator;
    }

    // Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good.
//进行迁移
    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;
    }

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

    // View function to see pending SUSHIs on frontend.
//获得矿池信息
    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);
    }

    // Update reward vairables for all pools. Be careful of gas spending!
//更新所有矿池信息
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update reward variables of the given pool to be up-to-date.
//更新某个矿池信息
    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(20));
        sushi.mint(address(this), sushiReward);
        pool.accSushiPerShare = pool.accSushiPerShare.add(sushiReward.mul(1e12).div(lpSupply));
        pool.lastRewardBlock = block.number;
    }

    // Deposit LP tokens to MasterChef for SUSHI allocation.
//存入LP
    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);
    }

    // Withdraw LP tokens from MasterChef.
//撤出令牌
    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);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
//无条件撤出令牌,用于矿池出现严重问题的时候
    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;
    }

    // Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs.
//安全转移sushi
    function safeSushiTransfer(address _to, uint256 _amount) internal {
        uint256 sushiBal = sushi.balanceOf(address(this));
        if (_amount > sushiBal) {
            sushi.transfer(_to, sushiBal);
        } else {
            sushi.transfer(_to, _amount);
        }
    }

    // Update dev address by the previous dev.
//设置管理员信息
    function dev(address _devaddr) public {
        require(msg.sender == devaddr, "dev: wut?");
        devaddr = _devaddr;
    }
}

重新编译和布置sushi的智能合约

说明一下,这部分的内容当时笔记做的不太好,大家当参考即可。 另外,由于我当时做相关开发的时候,sushi网站的源码并没有开源,所以用的是banana的网站源码。也就是说,sushi智能合约源码+banana网站源码

一 Token的安装

Token的安装比较简单 记得在最后面把名字改一下就行。(建议用yuno的,对比banana),用的是banana的。 contract PITAYAToken is ERC20("PITAYAswap","PITAYA"), Ownable {o:p 修改为Pitaya PITAYAToken (火龙果) Token:0x9527bf8828a991537558f33d291271b206376412o:p

二 chief

chief则需要修改很多的内容,而且是一次性完成的。(用的banana) A 需要修改这个BONUS_MULTIPLIER,变为2(表示最开始的发现是之后的2倍,sushi默认是10倍) B 一次性需要完成的是下面几个 SushiToken地址, devaddr地址(治理员,建议跟ower不能是一个,0x404331C629D53B500Ac06b596eaF533423A413a7) sushiPerBlock(每块创建的寿司代币数量,40暂定,输入40000000000000000000) bonusEndBlock(块结束时间,区块号,在这个后面就开始变少了,我们把这个时间设定为3个月左右10846048+600000=11446048) 也就是说,慢了10%。也就是说,真正持续的时间大概是三个月零3天。 startBlock(开始块的时间,开始才出口,预计设计为后天,10846048,12号九点) Ps:devaddr的作用主要是用来分成的,每挖一次都要给额外的10%,也可以用来提前偷摸挖矿,但咱们不干这个。 Chief:0x6a6db5fe904366023a0c0a9e2cea29ed8226b415o:p

三 建立后需要完成的两件事情

建立后需要完成的两件事情,注意对比yuno和banana。 第一个是Token合约的转换权限(transferownership),换成chief合约地址。 第二个是chef合约里面建立pool0和1(add函数,就是y.js里面的前几个列表,根据banana来就行,区别是改banana的第一个),剩余的慢慢来。Set函数可以修改对应的值。 Pool0:2,0xb23b4b10099690ff7e9ebe16d94c25124f9c4d07 true

重新编译和运行前端

因为我写笔记那会,sushi的前端还没有开源所以用的是yuno和banano的前端。

一 修改

a var chefAddress = "0x6a6db5fe904366023a0c0a9e2cea29ed8226b415";//改 b var tokenAddress = "0x9527bf8828a991537558f33d291271b206376412";//改o:p c ["0xb23b4b10099690ff7e9ebe16d94c25124f9c4d07", "UNISWAP NEST/ETH", "https://uniswap.info/pair/0xb23b4b10099690ff7e9ebe16d94c25124f9c4d07", 2, 0, 0],//改两个 *function getUniswapPrice() {

  • var ctx0 = new web3.eth.Contract(uniswapABI, pools[0][0]);
  • var ctx1 = new web3.eth.Contract(uniswapABI, uni1); var ctx2 = new web3.eth.Contract(uniswapABI, "0xb23b4b10099690ff7e9ebe16d94c25124f9c4d07");//改一个。 d http-server

    二 第一次使用

    进入到so部分,然后第一次设置会显示需要授权。 授权后就可以存入,建议存入少量。 存入后刷新页面,几秒钟到几分钟在staked token就可以出现了。 然后再取出。几次就ok了。

    三 启动测试与运行

    http-serve

    四 正常运行后的第三阶段

    等稳定了后,做两件事情。(暂时不做,kimchi做了这个,yuno,hotdog都没有做这部分).特别说明,所谓的稳定是在测试网测试过,搞清楚executeTransaction怎么用。 A 建立timelock,时间设定为delay(7天),管理员admin为我自己。 B 将chef合约的owner更改为timelock地址(这里还有很多问题需要解决起码还没搞清楚executeTransaction怎么用) C等完全都完事了,要部署GovernorAlpha,将timelock与挂接GovernorAlpha。就正式开启自治。

    五自治后运行的事情

    SushiSwap(SUSHI)发起流动性迁移提案#QmXm9T7,投票期限为 1 天。Timelock的管理者(本人)将部署所有必需的智能合约 Migrator、SushiSwapFactory(UniswapFactory)、SushiMaker 以及 SushiBar,并设置「setMigrator」功能来调用时间锁智能合约,将在 48 小时后完成迁移。一旦迁移完成,SushiSwap 将会借助自有的 AMM,把收取的交易费用的 1/6 分配给 SUSHI 代币持有者。

    注意事项

    一 分红从哪里来的。

    按照sushi的逻辑就是,用sushi进行分红,可以分25%,但这个东西是从后面的交易所得来的。而后期进行转换的时候,则需要最开始的一段时间过去才行。

    二 可能存在的风险

    慢雾创始人余弦发文分析,SushiSwap 仿盘项目 KIMCHI (泡菜) 项目方确实拥有任意铸币的权限,只是如果项目方要任意铸币,至少需要等待2天时间。对接泡菜的平台可以观测泡菜厨师的devaddr地址是否变更为泡菜厨师的当前owner地址。

    三 Migrator.sol

    就是从旧的工厂合约迁移到新的工厂合约里面去,里面有一个时间点,少于这个时间点是不可以的。

    四 部分源码说明

    Solidity中当签名不匹配任何的函数方法时,将会触发回退函数。比如,当你调用address.call(bytes4(bytes32(sha3("thisShouldBeAFunction(uint,bytes32)"))), 1, "test")时,EVM实际尝试调用地址中的thisShouldBeAFunction(),当这个函数不存在,会触发fallback函数。 call函数转账的使用方法是:地址.call.value(转账金额)()o:p

    五 令牌分红的本质

    Uniswap 中的 LP Token,对于每一个交易对,Uniswap 合约会给流动性提供者发行相应的 LP Token,LP Token 是日后流动性提供者赎回本金的凭证,当交易产生手续费时,手续费会自动打到 Pool 中,此时如果价格不变的话,相当于单位 LP Token 所能赎回的本金变多了,通过这种方法完成分红。

    六 布置合约的说明

    A 布置UniswapV2Factory.sol合约,其中那个地址就是我自己的地址(管理者) B 布置migrator(千万注意没有那个s),将UniswapV2Factory地址传输过去。还有一个旧地址,参考sushiswap里面的migraotr的oldfactory。布置将chef的owner转为timelock C Timelock.sol、migrator和GovernorAlpha.sol是一体的,用于治理。o:p D MasterChef里面有一个migrate合约,如果恶意放到不同的合约地址则有可能转走所有的币(在MasterChef里面的),Migrator合约的主要功能就是将uniswap里面的币转换为自己的池子里面并发新的币。这个里面需要注意的是,迁移的时间是需要大于一个值(这个值可以设计为很久),这样当set的时候,就可以由owner运行,而owner可以设计为可以换为另外一个,到时候则切换为GovernorAlpha投票进行即可

    七 部分解析

    1. timelock,governoralpha,migrator不着急做,合一起就是用治理委员会完成迁移。但现在还没有启用。
    2. MasterChef的owner给了timelock,Timelock的管理员(admin)可以设置一次,之后就锁死了,目前设置为某一个人;pendingAdmin目前还没有设置(为0)
    3. 目前最主要的方案就是masterchef里面的迁移,这个需要在一个规定时间后(比如是3个月,在migrator的初始化里面设置),由masterchef的owner进行。因为这种权限太危险,所以要把这个owner权限给timelock,同时设定timelock的admin为自己(pendingAdmin待定)。 4.特别提示: 等正式运行稳定了了,到了一定的规模,就将GovernorAlpha的权限转给timelock。在此之前一定要进行足够的风险提示。

      总结

      我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。本文首先对流动性挖矿(SUSHI)进行方案的解析,进而解决token是怎么来的问题。 关于如何具体的怎么增加矿池修改矿池的流程,当时笔记就没再记录详细,就没写。而且相关的文件都是半年前写的,很多内容未做校对,错误很多,因此在这里,大家仅做参考。

点赞 4
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
问答区块链
问答区块链
致力于人工智能与区块链相结合下的智能设备自治