我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。本文首先对流动性挖矿(SUSHI)进行方案的解析,进而解决token是怎么来的问题。
首先说一下。我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。
我的思路是这样的,如果把设备按功能分成不同的群体,那么群体内设备与设备之间通过token来完成信息的交互,这里最简单的例子就是基于预言机来完成信息的报价和购买,我们在《关于预言机nest的源码解析及克隆》里面已经对nest预言机部分的源码和克隆进行了解析,有兴趣的可以看一下。 接下来还有两个非常重要的内容需要解决。第一个是token是怎么来的,另一个是token之间是如何进行交互的。关于token是怎么来的,我们考虑的是模仿SUSHI的流动性挖矿来解决,而token之间是如何交易的,则模仿uniswap来完成。 本文首先对流动性挖矿(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的去中心化交易所
这个里面erc20部分大部分内容来自于openzeppelin,而// SushiToken with Governance.部分主要来自于yam。 大家理解为一个简单的可以挖矿的erc20币即可。 这里面的关键代码就一句:
contract PITAYAToken is ERC20("PITAYAswap", "PITAYA"), Ownable {
这里的PITAYAToken是我自己改的名字。后面的PITAYAswap是项目的名字,PITAYA是货币符号。 也就是说,用这段代码克隆的同学,就改这三个地方即可。
这个里面没有什么要改的,但要理解。 这个里面看似合约很多,但其实主要关注的只有一个,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网站的源码并没有开源,所以用的是banana的网站源码。也就是说,sushi智能合约源码+banana网站源码
Token的安装比较简单 记得在最后面把名字改一下就行。(建议用yuno的,对比banana),用的是banana的。 contract PITAYAToken is ERC20("PITAYAswap","PITAYA"), Ownable {o:p 修改为Pitaya PITAYAToken (火龙果) Token:0x9527bf8828a991537558f33d291271b206376412o:p
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() {
进入到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地址。
就是从旧的工厂合约迁移到新的工厂合约里面去,里面有一个时间点,少于这个时间点是不可以的。
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投票进行即可
我的开发项目是智能设备自治,其中有一个很关键的部分就是,物联网设备之前如何进行有价的信息交换。本文首先对流动性挖矿(SUSHI)进行方案的解析,进而解决token是怎么来的问题。 关于如何具体的怎么增加矿池修改矿池的流程,当时笔记就没再记录详细,就没写。而且相关的文件都是半年前写的,很多内容未做校对,错误很多,因此在这里,大家仅做参考。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!