6

SUSHI的源码及方案解析一(早期的流动性挖矿部分)

 3 years ago
source link: https://learnblockchain.cn/article/2128
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。本文首先对流动性挖矿(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

重新编译和运行banana的网站源码

基于banana的修改网站的代码,还有图片。这里记得要用yuno的对比改一下

一 修改

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是怎么来的问题。 关于如何具体的怎么增加矿池修改矿池的流程,当时笔记就没再记录详细,就没写。而且相关的文件都是半年前写的,很多内容未做校对,错误很多,因此在这里,大家仅做参考。

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

前言

我的思路是这样的,如果把设备按功能分成不同的群体,那么群体内设备与设备之间通过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

重新编译和运行banana的网站源码

基于banana的修改网站的代码,还有图片。这里记得要用yuno的对比改一下

一 修改

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是怎么来的问题。 关于如何具体的怎么增加矿池修改矿池的流程,当时笔记就没再记录详细,就没写。而且相关的文件都是半年前写的,很多内容未做校对,错误很多,因此在这里,大家仅做参考。

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

  • 发表于 7分钟前
  • 阅读 ( 9 )
  • 学分 ( 0 )
  • 分类:Uniswap

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK