├── .env.sample ├── .gitattributes ├── .gitignore ├── LICENSE ├── MasterChef.md ├── Migrator.md ├── README.md ├── SushiBar.md ├── SushiMaker.md ├── SushiToken.md ├── UniswapModify.md ├── contracts ├── GovernorAlpha.sol ├── MasterChef.sol ├── Migrations.sol ├── Migrator.sol ├── MockERC20.sol ├── OldMigrator.sol ├── SushiBar.sol ├── SushiMaker.sol ├── SushiToken.sol ├── Timelock.sol ├── WETH9.sol └── uniswapv2 │ ├── LICENSE │ ├── README.md │ ├── UniswapV2ERC20.sol │ ├── UniswapV2Factory.sol │ ├── UniswapV2Pair.sol │ ├── UniswapV2Router02.sol │ ├── interfaces │ ├── IERC20.sol │ ├── IUniswapV2Callee.sol │ ├── IUniswapV2ERC20.sol │ ├── IUniswapV2Factory.sol │ └── IUniswapV2Pair.sol │ └── libraries │ ├── Math.sol │ ├── SafeMath.sol │ └── UQ112x112.sol ├── migrations ├── 1_initial_migration.js ├── 2_deploy_SushiCore.js ├── 3_deploy_Uniswap.js ├── 4_deploy_Migrator.js ├── 5_deploy_SushiBar.js └── 6_deploy_SushiMaker.js ├── package-lock.json ├── package.json ├── test ├── AMasterChef.test.js ├── Migrator.test.js ├── SushiBar.test.js ├── SushiMaker.test.js ├── SushiToken.test.js ├── Timelock.test.js └── ZGovernor.test.js ├── truffle-config.js └── yarn.lock /.env.sample: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY=https://etherscan.io/myapikey这里注册 2 | infuraKey=infuraKey...到 https://infura.io 申请 3 | mnemonic=钱包助记词... -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .infuraKey 4 | .mnemonic 5 | .env -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2020 Chef Nomi 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /MasterChef.md: -------------------------------------------------------------------------------- 1 | # 主厨合约 2 | 3 | - 合约地址: https://etherscan.io/address/0xc2edad668740f1aa35e4d8f227fb8e17dca888cd 4 | 5 | - 交易hash https://etherscan.io/tx/0x3d68b0d8a94838af33070b8f00558e723f073b23772bd1760f1f4032e21e0fb3 6 | ## 构造函数 7 | 8 | - 构造函数中设置了以下变量: 9 | - Sushi Token的地址 10 | - 开发者帐号地址 11 | - 每个区块创建Sushi Token的数量 12 | - Sushi Token开始挖掘的区块号 13 | - 奖励结束的区块号 14 | - 对应的值如下 15 | - _sushi: '0x6B3595068778DD592e39A122f4f5a5cF09C90fE2' 16 | - _devaddr: '0xF942Dba4159CB61F8AD88ca4A83f5204e8F4A6bd' 17 | - _sushiPerBlock: '100000000000000000000' 18 | - _startBlock: '10750000' 19 | - _bonusEndBlock: '10850000' 20 | 21 | ## 状态变量,只读方法 22 | - sushi() Sushi Token的地址 23 | - devaddr() 开发者帐号地址 24 | - sushiPerBlock() 每个区块创建Sushi Token的数量 25 | - BONUS_MULTIPLIER() Sushi Token的奖金乘数 26 | - migrator() 迁移合约地址 27 | - poolInfo(uint256 池子id) 池子信息 28 | - userInfo(uint256 池子id,address 用户地址) 用户信息 29 | - totalAllocPoint() 总分配点,所有池中所有分配点的总和 30 | - startBlock() Sushi Token开始挖掘的区块号 31 | - poolLength() 返回池子数量 32 | - getMultiplier(uint256 from块号,uint256 to块号) 给出from和to的块号,返回奖励乘数 33 | - pendingSushi(uint256 池子id, address 用户地址) 查看功能以查看用户的处理中尚未领取的SUSHI 34 | 35 | ## 池子信息(重要) 36 | ``` 37 | struct PoolInfo { 38 | IERC20 lpToken; // LP代币(配对合约)的地址 39 | uint256 allocPoint; // 分配给该池的分配点数。 SUSHI按块分配 40 | uint256 lastRewardBlock; // SUSHIs分配发生的最后一个块号 41 | uint256 accSushiPerShare; // 每股累积SUSHI乘以1e12 42 | } 43 | ``` 44 | 45 | ## 用户信息(重要) 46 | ``` 47 | struct UserInfo { 48 | uint256 amount; // 用户提供了多少个lpToken。 49 | uint256 rewardDebt; // 已奖励数额。请参阅下面的说明。 50 | } 51 | // 我们在这里做一些有趣的数学运算。基本上,在任何时间点,授予用户但待分配的SUSHI数量为: 52 | // 待处理的奖励 =(用户添加的lpToken * 每股累积SUSHI)- 已奖励数额 53 | // 54 | // 每当用户将lpToken存入到池子中或提取时。这是发生了什么: 55 | // 1. 池子的每股累积SUSHI(accSushiPerShare)和分配发生的最后一个块号(lastRewardBlock)被更新 56 | // 2. 用户收到待处理奖励。 57 | // 3. 用户的“amount”数额被更新 58 | // 4. 用户的`rewardDebt`已奖励数额得到更新 59 | ``` 60 | ## 合约方法 61 | 62 | ### 名词示意 63 | - lpToken: Uniswap中每一个交易对都有一个配对合约,当你将两个Token存入到Uniswap交易所之后,Uniswap交易所会为你找到这个配对合约,并将你的两个Token存入到合约中,然后配对合约其实也是一个ERC20标准的Token合约,所以这个配对合约为了记录你存入的数额,就会为你生成配对合约的ERC20 Token,我们将这个Token称为lpToken,lp的意思是"流动性".在配对合约中lpToken的数额计算方法为:(tokenA数额*tokenB数额)的平方根.因为lpToken是标准的ERC20 token,所以就可以转让给另一个账户.在sushiSwap中存入的就是这个lpToken,当你将lpToken存入到sushiSwap之后,sushiSwap就有权利使用你的lpToken将Uniswap交易所的配对合约中的你存入的两个Token全部取出. 64 | 65 | ### add 将新的流动性Token添加到池中 66 | - 这个方法只能由所有者调用 67 | 68 | - 所有者通过这个方法将新的uniswap交易对的流动性token配对合约地址添加到池中,创建新的流动性挖矿池子 69 | ``` 70 | 参数 71 | uint256 _allocPoint //分配给该池的分配点数。 SUSHI按块分配 72 | IERC20 _lpToken //lpToken合约的地址 73 | bool _withUpdate //触发更新所有池的奖励变量。注意gas消耗! 74 | ``` 75 | 1. 如果_withUpdate的值为真,则触发更新所有池子奖励的方法 76 | 2. 定义分配发生的最后一个块号 77 | - 新池子的分配发送最后块号应该是当期区块号 78 | - 如果当前区块号没到开始挖矿的区块号,则取开始挖矿的区块号 79 | 3. 将参数中的分配点数入到总分配点 80 | 4. 将池子信息定义到构造体中,并推入池子数组 81 | 82 | ### set 更新给定池的SUSHI分配点 83 | - 这个方法只能由所有者调用 84 | 85 | - 通过这个方法调整现有池子的SUSHI分配点 86 | ``` 87 | 参数 88 | uint256 _pid //池子ID,池子数组中的索引 89 | uint256 _allocPoint //新的分配给该池的分配点数。 SUSHI按块分配 90 | bool _withUpdate //触发更新所有池的奖励变量。注意gas消耗! 91 | ``` 92 | 1. 如果_withUpdate的值为真,则触发更新所有池子奖励的方法 93 | 2. 根据参数的新分配点数,将总分配点数调整正确,方法是将总分配点数减去给定池子的旧分配点数,再加上新分配点数,这样不管新分配点数比旧分配点数大还是小都能确保调整正确 94 | 3. 调整池子信息映射中的池子构造体中的分配点数 95 | 96 | ### setMigrator 设置迁移合约地址 97 | - 这个方法比较简单,只负责将migrator变量设置成新的值 98 | ``` 99 | 参数 100 | IMigratorChef _migrator //合约地址 101 | ``` 102 | 103 | ### migrate 迁移方法 104 | - 将lp令牌迁移到另一个lp合约。可以被任何人呼叫。我们相信迁移合约是正确的 105 | 106 | - 这个方法用作将主厨合约持有的指定池子中的lp Token(uniswap的配对合约)批准给迁移合约,然后调用迁移合约的迁移方法,迁移的工作过程请看迁移合约的文档 107 | ``` 108 | 参数 109 | uint256 _pid //池子id,池子数组中的索引 110 | ``` 111 | 1. 确认迁移合约地址已经被设置 112 | 2. 查询到主厨合约在给定池子的lpToken的余额 113 | 3. 调用迁移合约,并执行迁移方法,返回新的SushiSwap的lpToken地址 114 | 4. 查询并确认新lpToken合约中当前主厨合约地址的余额,与旧的uniswap合约余额相等 115 | 116 | ### getMultiplier 给出from和to的块号,返回奖励乘积 117 | - 奖励乘数是指在奖励区块内,奖励的倍数,当一个用户要取出奖励的时候,他有可能有一部分未取出金额在奖励区块范围内,一部分不在,或者都在,或者都不在,所以要通过这个方法计算出一个乘积 118 | ``` 119 | 参数 120 | uint256 _from //from块号 121 | uint256 _to //to块号 122 | ``` 123 | 1. 如果to块号小于奖励结束的块号,说明整个区间都在奖励范围内,则返回from-to的块数乘以固定奖励乘数(10倍) 124 | 2. 如果from大于奖励结束的块号,说明整个区间都在奖励范围之外,则返回from-to的块数 125 | 3. 其他情况说明一部分在奖励区块内,一部分不在,所以先取得奖励区块内的范围(奖励结束块号-from块号)再乘以奖励乘数(10倍),然后再加上奖励区块外的块数(to块号-奖励结束块号),然后就可以获得正确的奖励乘积 126 | 4. 返回乘积 127 | 128 | ### pendingSushi 查看功能以查看用户的处理中尚未领取的SUSHI 129 | - 领取sushiToken需要写入操作触发,所以在没有写入操作触发之前,用户的sushiToken处于pending状态,这个方法可以计算出用户处在pending状态的sushiToken 130 | 131 | - 计算方法的本质是通过池子上次最后奖励的区块到当前区块的数量计算出整个池子应得的sushi数量,然后再计算出池子中每股累计sushi奖励数值,最后使用用户存入的lpToken数值乘以每股奖励数值,再减去用户已领取的奖励数值计算出尚未领取的sushi数值 132 | ``` 133 | 参数 134 | uint256 _pid //池子id 135 | address _user //用户地址 136 | ``` 137 | 1. 实例化池子信息,实例化用户信息 138 | 2. 取出每股累计Sushi,每一个lpToken累计获得过多少Sushi 139 | 2. 计算当前主厨合约在给定的池子(uniswap配对合约)中的lpToken余额 140 | 3. 先判断当前区块是否大于池子的最后奖励区块 141 | - 计算出奖金乘积,通过池子最后奖励区块作为from,当前区块作为to得到奖金乘积 142 | - 计算sushi奖励,用奖金乘积,乘以sushi每块奖励,乘以池子的分配点数,除以所有池子总点数(取得池子站全部sushiToken分配的占比) 143 | - 计算每股累计sushi,使用sushi的奖励除以lpToken的余额累计获得 144 | 4. 返回,尚未领取的sushi数额等于:用户已添加到池子中的lpToken数额,乘以每股累计奖励Sushi的数额,减去用户已领取的奖励数额 145 | 146 | ### massUpdatePools 更新所有池的奖励变量 147 | - 这个方法比较简单,通过池子数组的长度遍历池子数组,然后执行升级池子的方法 148 | 1. 取出池子数组长度 149 | 2. 循环遍历池子数组 150 | - 根据池子id升级池子数组 151 | 152 | ### updatePool 将给定池的奖励变量更新为最新 153 | - 这个方法的本质是通过上次最后奖励的区块到当前的区块数量计算出池子应得的sushi奖励数额,然后再计算出池子中每股累计sushi奖励数值,池子信息中只记录每股累计奖励数值,不记录sushi奖励数值 154 | ``` 155 | 参数 156 | uint256 _pid //池子id 157 | ``` 158 | 1. 实例化池子信息 159 | 2. 如果当前区块小于给定池子的分配发生的最后一个区块,则返回(终止运行,为了防止意外发生) 160 | 3. 计算当前主厨合约在给定的池子(uniswap配对合约)中的lpToken余额 161 | 4. 如果lpToken余额为0,更新池子信息中分配发生的最后一个区块号为当前区块号,然后返回(终止运行) 162 | 5. 计算出奖金乘积,通过池子最后奖励区块作为from,当前区块作为to得到奖金乘积 163 | 6. 计算sushi奖励,用奖金乘积,乘以sushi每块奖励,乘以池子的分配点数,除以所有池子总点数(取得池子站全部sushiToken分配的占比) 164 | 7. 为开发者账号铸造sushiToken,数量为sushi奖励除以10(10为固定值不可更改) 165 | 8. 为当前主厨合约铸造sushiToken,数量为sushi奖励数额 166 | 9. 计算每股累计sushi,使用sushi的奖励除以lpToken的余额累计获得 167 | 10. 更新池子信息中的分配发生的最后一个区块 168 | 169 | ### deposit 用户将lpToken存入主厨合约并进行SUSHI分配 170 | - 用户通过这个方法将Uniswap的Lptoken存入到主厨合约 171 | 172 | - 如果当前用户之前存入过lpToken则将之前应获得的sushi奖励发送到用户账户中 173 | ``` 174 | 参数 175 | uint256 _pid //池子id 176 | uint256 _amount //数额 177 | ``` 178 | 1. 实例化池子信息,实例化用户信息 179 | 2. 取出每股累计Sushi,每一个lpToken累计获得过多少Sushi 180 | 3. 通过updatePool方法,将给定池的奖励变量更新为最新 181 | 4. 如果用户已添加的数额>0 182 | - 计算用户处理中的sushi奖励数额(通过用户存入的lpToken数额*池子的每股累计奖励sushi-用户已领取的sushi数额) 183 | - 向调用者发送sushiToken,数额为处理中的奖励数额 184 | 5. 调用lpToken的安全发送方法,从调用者账户发送数额为_amount的lpToken到当前合约地址(调用方法前需要用户appove批准lpToken给主厨合约大于等于_amount的数额) 185 | 6. 累加用户已添加的数额 186 | 7. 计算用户已领取sushi的奖励数额,通过用户已添加的数额乘以池子中的每股奖励sushi数额(如果用户第一次添加,也将会有已奖励数额,目的为了排除第一次添加之前的数额) 187 | 188 | ### withdraw 用户从主厨合约提取lpToken 189 | - 用户通过这个方法将主厨合约中的unswap都lpToken取出到自己的账户中 190 | 191 | - 如果用户有处理中的sushi奖励,则发送给用户账户 192 | ``` 193 | 参数 194 | uint256 _pid //池子id 195 | uint256 _amount //数额 196 | ``` 197 | 1. 实例化池子信息,实例化用户信息 198 | 2. 确认用户已添加的数额大于等于将要取出的数额 199 | 3. 通过updatePool方法,将给定池的奖励变量更新为最新 200 | 4. 计算用户处理中的sushi奖励数额(通过用户存入的lpToken数额*池子的每股累计奖励sushi-用户已领取的sushi数额) 201 | 5. 向调用者发送sushiToken,数额为处理中的奖励数额 202 | 6. 用户已添加的数额减去将要取出的数额 203 | 7. 计算用户已领取sushi的奖励数额,通过用户取出后剩余的数额乘以池子中的每股奖励sushi数额 204 | 8. 调用lpToken的安全发送方法,从当前合约地址发送数额为_amount的lpToken到调用者账户 205 | 206 | ### emergencyWithdraw 紧急提取方法,仅限紧急情况 207 | - 调用这个方法将会把用户存在当前主厨合约的lpToken数额提取到自己的账户中,如果用户存在处理中的sushi奖励将会被忽略 208 | ``` 209 | 参数 210 | uint256 _pid //池子id 211 | ``` 212 | 1. 实例化池子信息,实例化用户信息 213 | 2. 调用lpToken的安全发送方法,从当前合约地址发送数额为调用者账户全部存入数额的lpToken到调用者账户 214 | 3. 触发紧急提款事件 215 | 4. 为用户的存入数额和已领取sushi奖励的数额都归零 216 | 217 | ### safeSushiTransfer 安全的sushi转移功能 218 | - 这个方法是为了防止要发送的sushi Token数额大于当前合约在sushi Token的余额而设计的 219 | ``` 220 | 参数 221 | address _to //to地址 222 | uint256 _amount //数额 223 | ``` 224 | 1. 计算当前主厨合约在sushi TOken的余额 225 | 2. 判断如果_amount要发送的数额大于当前合约的余额 226 | - 调用sushi TOken的发送方法,从当前合约向_to地址发送数量为当前合约全部余额的sushi 227 | 3. 否则 228 | - 调用sushi TOken的发送方法,从当前合约向_to地址发送数量_amount的sushi 229 | 230 | ### dev 更新开发者账号 231 | - 通过先前的开发者地址更新开发者地址 232 | ``` 233 | 参数 234 | address _devaddr //开发者地址 235 | ``` 236 | 1. 确认调用者为当前_devaddr地址 237 | 2. 修改devaddr为参数中的_devaddr地址 238 | 239 | -------------------------------------------------------------------------------- /Migrator.md: -------------------------------------------------------------------------------- 1 | # 迁移合约 2 | 3 | - 合约地址: https://etherscan.io/address/0x93ac37f13bffcfe181f2ab359cc7f67d9ae5cdfd 4 | 5 | - 交易hash https://etherscan.io/tx/0x2e93327b9a1b65c10ab20679a4b52cb657e3c617023afaefb5a04a1a0e261ac6 6 | ## 构造函数 7 | 8 | - 构造函数中设置了以下变量: 9 | - 主厨合约地址 10 | - 旧工厂合约地址(Uniswap工厂合约) 11 | - 新工厂合约 12 | - 不能早于的块号 13 | - 对应的值如下 14 | - _chef: '0xc2edad668740f1aa35e4d8f227fb8e17dca888cd' 15 | - _oldFactory: '0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f' 16 | - _factory: '0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac' 17 | - _notBeforeBlock: '0' 18 | 19 | ## 状态变量 20 | - chef() 主厨合约地址 21 | - oldFactory() Uniswap工厂合约地址 22 | - factory() SushiSwap新工厂合约 23 | - notBeforeBlock() 不能早于的块号 24 | - desiredLiquidity() 需求流动性数额 = 无限大 25 | 26 | ## migrate 迁移方法 27 | - 这个方法简要概括实现的是将主厨合约的lpToken发送给Uniswap配对合约,然后执行Uniswap配对合约的销毁方法 28 | - Uniswap的销毁方法执行后,会将配对合约账户中的两个token返还给指定账户(这里就是SushiSwap新配对合约) 29 | - 随后执行SushiSwap新配对合约的铸造方法,为主厨合约铸造SushiSwap新的lpToken 30 | ``` 31 | 参数 32 | IUniswapV2Pair orig //Uniswap配对合约地址 33 | ``` 34 | 1. 确认当前用户是主厨合约地址,并且发起交易的地址为SBF的帐号地址(说明是SBF发起的交易,通过主厨合约调用的迁移合约) 35 | 2. 确认当前块号大于不能早于的块号 36 | 3. 确认配对合约的工厂合约地址等于旧工厂合约地址,通过调用旧配对合约的工厂合约变量查询到的 37 | 4. 从Uniswap配对合约中查询到token0和token1,两个token是按照16进制数字的大小排序的 38 | 5. 通过SushiSwap工厂合约的获取配对方法获取配对合约地址 39 | 6. 如果获取到的配对合约地址为0地址,说明配对合约不存在,则调用SushiSwap工厂合约的创建配对方法,创建token0和token1的配对合约 40 | 7. 确认配对合约的总量为0,说明之前没有执行过迁移 41 | 8. 获取当前用户(主厨合约)在Uniswap配对合约的lpToken余额 42 | 9. 如果流动性数量为0,直接返回配对合约地址(终止运行) 43 | 10. 定义需求流动性数额等于获取到的Uniswap配对合约流动性数量 44 | 11. 调用Uniswap配对合约的发送方法,从主厨合约将流动性数量发送到旧配对合约(在主厨合约中已主厨合约已将数额批准给了迁移合约) 45 | 12. 调用Uniswap配对合约的销毁方法,销毁方法会将token0,token1发送到SushiSwap的新配对合约 46 | 13. 调用SushiSwap新配对合约的铸造方法给主厨合约铸造新流动性token 47 | 14. 将需求的流动性数额调整回无限大 48 | 15. 返回SushiSwap新配对合约地址 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SushiSwap 🍣 中文注释,中文文档 2 | 3 | - via 崔棉大师 4 | 5 | https://app.sushiswap.org. Feel free to read the code. More details coming soon. 6 | 7 | ## 中文文档 8 | 9 | - [MasterChef主厨合约文档](./MasterChef.md) 10 | - [SushiToken文档](./SushiToken.md) 11 | - [Migrator迁移合约文档](./Migrator.md) 12 | - [SushiMaker文档](./SushiMaker.md) 13 | - [SushiBar文档](./SushiBar.md) 14 | - [Uniswap合约修改文档](./UniswapModify.md) 15 | 16 | ## 合约文件中文注释 17 | 18 | - [MasterChef主厨合约](./contracts/MasterChef.sol) 19 | - [SushiToken合约](./contracts/SushiToken.sol) 20 | - [Migrator迁移合约](./contracts/Migrator.sol) 21 | - [SushiMaker合约](./contracts/SushiMaker.sol) 22 | - [SushiBar合约](./contracts/SushiBar.sol) 23 | - [Uniswap工厂合约](./contracts/uniswapv2/UniswapV2Factory.sol) 24 | - [Uniswap配对合约](./contracts/uniswapv2/UniswapV2Pair.sol) 25 | 26 | ## SushiSwap合约布署顺序 27 | 28 | ### 首先运行命令 29 | - 在项目目录运行命令安装依赖后才可以运行布署脚本 30 | 31 | ``` 32 | $ npm install 33 | ``` 34 | 35 | ### 布署说明 36 | > 通过修改对应布署脚本中的参数实现定制自己的SushiSwap 37 | 1. 布署SushiToken,没有构造函数,SushiToken初始代币总量为0 38 | 2. 布署主厨合约,构造函数中需要SushiToken的地址和开发者账号地址,还需要定义开始区块等参数 39 | - [布署脚本2](./migrations/2_deploy_SushiCore.js) 40 | 3. 可以开始运行质押挖矿了,直到挖矿期结束,开始迁移工作 41 | 4. 布署Uniswap工厂合约,构造函数为收税地址管理员账号,这个账号可以设置税款接收地址,目前为SBF掌握 42 | 5. 布署Uniswap路由合约,构造函数为工厂合约地址和WETH地址 43 | - [布署脚本3](./migrations/3_deploy_Uniswap.js) 44 | 6. 布署迁移合约,构造函数中包括主厨合约地址,Uniswap工厂合约地址,SushiSwap工厂合约地址和执行迁移不能早于的区块号 45 | - [布署脚本4](./migrations/4_deploy_Migrator.js) 46 | 7. 现在可以执行迁移操作了 47 | 8. 布署SushiBar合约,构造函数中为SushiToken的合约地址 48 | - [布署脚本5](./migrations/5_deploy_SushiBar.js) 49 | 9. 布署SushiMaker合约,构造函数中为SushiSwap工厂合约地址,SushiBar合约地址,SushiToken的合约地址,WETH合约地址,只有要把SushiSwap工厂合约的feeTo地址设置为SushiMaker的地址 50 | - [布署脚本6](./migrations/6_deploy_SushiMaker.js) 51 | 10. 现在SushiSwap已经可以正常运行了,0.05%的手续费税款会转到SushiMaker的地址,通过调用SushiMaker的合约方法可以将手续费税款对应的资产一步操作全部购买成SushiToken,然后会将SushiToken转到SushiBar合约 52 | ### 布署命令 53 | 1. 将项目目录中的.env.sample文件修改文件名为.env,编辑这个文件设置infuraKey和mnemonic助记词 54 | 2. 在项目目录运行以下命令可以布署,修改脚本编号,网络名称可以修改为"mainnet"就是以太坊主网,"ropsten,rinkeby,goerli,kovan"为4个测试网,"ganache"为本地测试环境 55 | ``` 56 | $ truffle migrate -f <脚本编号> -t <相同的脚本编号> --network <网络名称> 57 | ``` 58 | 3. 本地测试环境可以通过以下命令打开 59 | ``` 60 | $ npm run ganache 61 | ``` 62 | 63 | ### SushiSwap 前端修改 64 | - 修改文件sushiswap-frontend/src/sushi/lib/constants.js 65 | ``` 66 | // 替换成自己的SushiToken地址和主厨合约地址即可 67 | export const contractAddresses = { 68 | sushi: { 69 | 1: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2', 70 | }, 71 | masterChef: { 72 | 1: '0xc2edad668740f1aa35e4d8f227fb8e17dca888cd', 73 | }, 74 | weth: { 75 | 1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 76 | }, 77 | } 78 | ``` 79 | ## SushiSwap合约地址/Hash 80 | 81 | - 主厨Nomi的地址 - https://etherscan.io/address/0xf942dba4159cb61f8ad88ca4a83f5204e8f4a6bd 82 | - SushiToken - https://etherscan.io/token/0x6b3595068778dd592e39a122f4f5a5cf09c90fe2 83 | - MasterChef - https://etherscan.io/address/0xc2edad668740f1aa35e4d8f227fb8e17dca888cd 84 | - (Uni|Sushi)swapV2Factory - https://etherscan.io/address/0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac 85 | - (Uni|Sushi)swapV2Router02 - https://etherscan.io/address/0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f 86 | - (Uni|Sushi)swapV2Pair init code hash - `e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303` 87 | - SushiBar - https://etherscan.io/address/0x8798249c2e607446efb7ad49ec89dd1865ff4272 88 | - SushiMaker - https://etherscan.io/address/0x54844afe358ca98e4d09aae869f25bfe072e1b1a 89 | - MultiSigWalletWithDailyLimit - https://etherscan.io/address/0xf73b31c07e3f8ea8f7c59ac58ed1f878708c8a76 90 | - Timelock - https://etherscan.io/address/0x9a8541ddf3a932a9a922b607e9cf7301f1d47bd1 91 | - old migrator - https://etherscan.io/address/0x818180acb9d300ffc023be2300addb6879d94830 92 | - migrator - https://etherscan.io/address/0x93ac37f13bffcfe181f2ab359cc7f67d9ae5cdfd 93 | 94 | ## 大厨操作 95 | - setFeeToSetter - https://etherscan.io/tx/0x2032ce062801e5d9ba03d7717491df6eaba513e5ae536cb97726f58daa66cd92 96 | > 将feeToSetter地址设置为 0xd57581d9e42e9032e6f60422fa619b4a4574ba79 97 | - transferOwnership https://etherscan.io/tx/0x414204c5bd062c86812b9bf5bedadd96c370a743f095430a413c961105adc8ac 98 | > nomi将主厨合约的owner身份转移到时间锁合约 99 | - queueTransaction - https://etherscan.io/tx/0xf5d8251f7fbb8b8d64607e7538f644b3eb1cb11864d7490821df6e4f88bac1e3 100 | > 在时间锁合约中提交setMigrator交易,交易将在48小时后执行 101 | - setPendingAdmin - https://etherscan.io/tx/0x8e2f3f27e616d8be2d2d3095a996cf4c0af8c9c757c7ff034d352c11cc082394 102 | > 将时间锁合约管理员设置为0xd57581d9e42e9032e6f60422fa619b4a4574ba79 103 | - 抛售的交易 - https://etherscan.io/tx/0x419a835b33eb03481e56a5f964c1c31017ab196cb7bb4390228cabcf50dfd6f1 104 | 105 | ## SBF操作 106 | - 地址 - https://etherscan.io/address/0xd57581d9e42e9032e6f60422fa619b4a4574ba79 107 | - acceptAdmin - https://etherscan.io/tx/0x251508ad94261ed3de6eff3e86bf888a4b40ce49fdbe29189e6d48d7b6c6804b 108 | > 接受时间锁合约的管理员 109 | - cancelTransaction - https://etherscan.io/tx/0x1c95d23fad620274971323e09bbb425b17169927c13e2554a175aa9da974f4f9 110 | > 取消时间锁合约的setMigrator交易 111 | - setMigrator - https://etherscan.io/tx/0xafb807819d00fd1f4a6ba4ef17370acb4ef39f199e6930e462bcd75de63244d2 112 | > 执行sushi工厂合约中的setMigrator方法,在工厂合约中设置迁移合约地址,此方法为将来运行交易所做准备,并不能执行迁移操作 113 | - queueTransaction - https://etherscan.io/tx/0x416a19f54d85de00b5cfcb7f498e61e5867b2a88e981c8396ea3e27ab7388cac 114 | > 重新提交setMigrator交易,将迁移合约地址设置为0x93ac37f13bffcfe181f2ab359cc7f67d9ae5cdfd 115 | - 500个ETH巨额交易费的交易 116 | > https://cn.etherscan.com/tx/0x7ef94acf19eaff3517e0675db1d6694b7567e79090cb1192f20ad0ee7892078d 117 | 118 | ## License 119 | 120 | WTFPL -------------------------------------------------------------------------------- /SushiBar.md: -------------------------------------------------------------------------------- 1 | # SushiBar合约 2 | - SushiBar是一个接收用户转账SushiToken然后转换为xSUSHI的合约,xSUSHI的数额相当于你在SushiBar的份额,可以随时进入和退出 3 | - 进入之后你的xSUSHI为存入的SushiToken*(xSUSHI总发行量/合约的SushiToken余额) 4 | - 合约地址: https://etherscan.io/address/0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272 5 | 6 | - 交易hash https://etherscan.io/tx/0x7fdb7a46473bcb6748c597f5f299ff76fe059294a6e4f8e9bdb0b5fe643ff92e 7 | ## 构造函数 8 | 9 | - 构造函数中设置了以下变量: 10 | - Sushi Token地址 11 | - 对应的值如下 12 | - _sushi: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2' 13 | 14 | ## 状态变量 15 | - sushi() Sushi Token地址 16 | 17 | ## ERC20标准方法略 18 | 19 | ## 合约方法 20 | 21 | ### enter 进入吧台 22 | - 将自己的sushiToken发送到合约换取份额 23 | ``` 24 | 参数 25 | uint256 _amount //SushiToken数额 26 | ``` 27 | 1. 计算当前SushiBar合约的SushiToken余额 28 | 2. 计算当前SushiBar合约的总发行量 29 | 3. 如果SushiToken余额或者SushiBar合约的总发行量有一个为0 30 | - 为调用者账户铸造数量为_amount的xSUSHI 31 | 4. 否则 32 | - 按照xSUSHI总发行量/合约的SushiToken余额的比例关系将存入数额_amount计算成`份额` 33 | - 为调用者账户铸造数量为`份额`的xSUSHI 34 | 5. 将调用者的SushiToken发送到当前合约 35 | 36 | ### leave 离开吧台 37 | - 取回自己的sushiToken 38 | ``` 39 | 参数 40 | uint256 _share //SUSHIs数额 41 | ``` 42 | 1. 计算当前SushiBar合约的总发行量 43 | 2. 按照合约的SushiToken余额/xSUSHI总发行量的比例关系将SUSHIs数额计算成SushiToken数额 44 | 3. 为调用者销毁SUSHIs数额 45 | 5. 将当前合约的SushiToken发送到调用者账户 46 | 47 | -------------------------------------------------------------------------------- /SushiMaker.md: -------------------------------------------------------------------------------- 1 | # SushiMaker合约 2 | - 这个合约的主要目的是可以让用户将任意两个token的交易对的lpToken交给SushiMaker合约,然后SushiMaker合约会将两个token全部转换为SushiToken并发送到SushiBar合约 3 | - 合约地址: https://etherscan.io/address/0x54844afe358Ca98E4D09AAe869f25bfe072E1B1a 4 | 5 | - 交易hash https://etherscan.io/tx/0xd0d44d714ca19a5b1bc37308761f6763472c21f13270a18b3168705f637bbc33 6 | ## 构造函数 7 | 8 | - 构造函数中设置了以下变量: 9 | - _factory 工厂合约地址 10 | - _bar Sushi Bar地址 11 | - _sushi Sushi Token地址 12 | - _weth WETH地址 13 | - 对应的值如下 14 | - _factory: '0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac' 15 | - _bar: '0x8798249c2e607446efb7ad49ec89dd1865ff4272' 16 | - _sushi: '0x6b3595068778dd592e39a122f4f5a5cf09c90fe2' 17 | - _weth: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' 18 | 19 | ## 状态变量 20 | - factory() 工厂合约地址 21 | - bar() Sushi Bar地址 22 | - sushi() Sushi Token地址 23 | - weth() WETH地址 24 | 25 | ## 合约方法 26 | 27 | ### convert 转换方法 28 | - 在调用转换方法之前需要将token0,token1对应的配对合约中的lpToken发送到当前SuShiMaker合约账户中 29 | ``` 30 | 参数 31 | address token0 //token0 32 | address token0 //token1 33 | ``` 34 | 1. 确认合约调用者为初始调用用户,防止通过合约调用此方法 35 | 2. 通过token0和token1找到配对合约地址,并实例化配对合约 36 | 3. 调用配对合约的transfer方法,将当前合约的lpToken余额发送到配对合约地址上 37 | 4. 调用配对合约的销毁方法,将流动性token销毁,之后配对合约将会向当前合约地址发送token0和token1 38 | 5. 将token0和token1全部交换为WETH并发送到WETH和SushiToken的配对合约上 39 | 6. 将WETH全部交换为Sushi Token并发送到Sushi 1Bar合约上 40 | 41 | ### _toWETH 将token卖出转换为WETH 42 | - 将任意一种token转换为WETH 43 | - 如果token是WETH则直接返回数额 44 | - 如果token是SushiToken则返回0 45 | ``` 46 | 参数 47 | address token //token 48 | ``` 49 | 1. 如果token地址是Sushi Token地址 50 | - 向SushiBar合约发送当前合约地址在token地址上的余额 51 | - 返回0(终止运行) 52 | 2. 如果token地址是WETH地址 53 | - 将当前合约地址在token地址上的余额,从当前合约发送到WETH和SushiToken的配对合约地址上(等待后面的购买操作) 54 | - 返回当前合约地址在token地址上的余额(终止运行) 55 | 3. 实例化token地址和WETH地址的配对合约 56 | 4. 如果配对合约地址为0地址,返回0(终止运行) 57 | 5. 从配对合约获取储备量0,储备量1 58 | 6. 找到token0(根据地址排序) 59 | 7. 排序形成储备量In和储备量Out 60 | 8. 定义输入数额为当前合约地址在token地址的余额 61 | 9. 税后输入数额为输入数额 * 997 62 | 10. 分子为税后输入数额 * 储备量Out 63 | 11. 分母为储备量In * 1000 + 税后输入数额 64 | 12. 输出数额为分子 / 分母 65 | 13. 排序输出数额0和输出数额1,有一个是0 66 | 14. 将输入数额发送到配对合约 67 | 15. 执行配对合约的交换方法(输出数额0,输出数额1,发送到WETH和token的配对合约上),相当于用卖出token换取WETH 68 | 16. 返回输出数额(WETH数额) 69 | 70 | ### _toSUSHI 将WETH交换为SushiToken 71 | - 这个方法将WETH交换为SushiToken并发送到SushiBar合约 72 | ``` 73 | 参数 74 | uint256 amountIn //输入数额 75 | ``` 76 | 1. 获取SushiToken和WETH的配对合约地址,并实例化配对合约 77 | 2. 获取配对合约的储备量0,储备量1 78 | 3. 找到token0 79 | 4. 排序生成储备量In和储备量Out 80 | 5. 税后输入数额为输入数额 * 997 81 | 6. 分子为税后输入数额 * 储备量Out 82 | 7. 分母为储备量In * 1000 + 税后输入数额 83 | 8. 输出数额为分子 / 分母 84 | 9. 排序输出数额0和输出数额1,有一个是0 85 | 10. 执行配对合约的交换方法(输出数额0,输出数额1,发送到sushiBar合约上) 86 | -------------------------------------------------------------------------------- /SushiToken.md: -------------------------------------------------------------------------------- 1 | # SushiToken合约 2 | 3 | - 合约地址: https://etherscan.io/address/0x6b3595068778dd592e39a122f4f5a5cf09c90fe2 4 | 5 | - 交易hash https://etherscan.io/tx/0x5489c98aa634078471646e32a3a846c8d413f055ce10d06bd2260f4e71d1bc63 6 | 7 | ## 没有构造函数 8 | ## ERC20标准方法略 9 | 10 | ## 状态变量,只读方法 11 | 12 | - checkpoints(address 用户地址, uint32 索引) 返回检查点构造体 13 | - numCheckpoints(address 用户地址) 每个帐户的`检查点数`映射,地址=>数额 14 | - DOMAIN_TYPEHASH() EIP-712的合约域hash 15 | - DELEGATION_TYPEHASH() EIP-712的代理人构造体的hash 16 | - nonces(address 用户地址) 返回用于签名/验证签名的状态记录(nonce值) 17 | - delegates(address 被委托的地址) 查询被委托的地址的委托人 18 | ## 易混淆的重要变量 19 | 20 | ### 检查点构造体 21 | - 一个检查点,用于标记给定块中的投票数 22 | ``` 23 | struct Checkpoint { 24 | uint32 fromBlock; // 开始区块号 25 | uint256 votes; // 票数 26 | } 27 | ``` 28 | ### 检查点数映射 29 | - 账户地址 => 检查点的数量 30 | - `检查点数`映射,用于记录`检查点`映射中用户有多少个检查点 31 | - `检查点数`映射中的`数量从1开始`,`检查点`映射中用户的检查点`索引值从0开始` 32 | ``` 33 | mapping (address => uint32) public numCheckpoints 34 | ``` 35 | ### 检查点映射 36 | - 账户地址 => 检查点索引 => 检查点构造体 37 | - `检查点`映射,用于保存用户的每一个`检查点构造体` 38 | - `检查点`映射中用户的检查点`索引值从0开始` 39 | ``` 40 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 41 | ``` 42 | > 下文中注意区别`检查点`映射和`检查点数`映射,还有`检查点构造体` 43 | ## 合约方法 44 | 45 | ### mint 铸造方法 46 | - 只能由所有者(主厨合约)调用 47 | ``` 48 | 参数 49 | address _to //接收地址 50 | uint256 _amount //数额 51 | ``` 52 | 1. 调用ERC20的铸造方法 53 | 2. 调用移动委托方法,将铸造出来的数量添加到委托人的票数中 54 | 55 | ### delegate 转移当然用户的委托人 56 | ``` 57 | 参数 58 | address delegatee //委托人地址 59 | ``` 60 | 1. 调用私有的转移委托人方法,参数为调用者账户和委托人地址 61 | 62 | ### delegateBySig 从签署人到delegatee的委托投票 63 | - 这个方法和delegate方法的区别是:delegate将修改调用者的委托人,delegateBySig方法可以让一个用户通过签名的方法让另一个账户修改自己的委托人 64 | ``` 65 | 参数 66 | address delegatee //委托人地址 67 | uint nonce //nonce值,匹配签名所需的合同状态 68 | uint expiry //签名到期的时间 69 | uint8 v //签名的恢复字节 70 | bytes32 r //ECDSA签名对的一半 71 | bytes32 s //ECDSA签名对的一半 72 | ``` 73 | 1. 将域hash + 名字hash + chainId + 当前合约地址打包哈希得到域分割 74 | 2. 将构造体的hash + 委托人地址 + nonce值 + 过期时间打包哈希得到构造体hash 75 | 3. 将域分割 + 构造体hash打包哈希得到签名前数据 76 | 4. 通过v,r,s和签名前数据使用ecrecover方法恢复签名地址 77 | 5. 验证签名地址和nonce值,过期时间都正确 78 | 6. 调用并返回更换委托人方法 79 | 80 | ### getCurrentVotes 获取给定账户地址的当前剩余票数 81 | - 这个方法返回`检查点`映射中用户最后一个检查点的票数 82 | ``` 83 | 参数 84 | address account //账户地址 85 | ``` 86 | 1. 通过`检查点数`映射查询账户地址的检查点 87 | 2. 如果检查点数量大于0 88 | - 返回选票`检查点`映射中账户地址最后一个检查点的票数 89 | 3. 否则返回0 90 | 91 | ### getPriorVotes 确定帐户在指定区块前的投票数 92 | - 账户在指定区块的票数保存在`检查点`映射中 93 | - 判断如果`检查点数量`为0,返回0 94 | - 如果`检查点`映射中最后一个`检查点构造体`中的区块号比给入区块号小,则返回`检查点`映射中最后一个`检查点构造体`中的票数 95 | - 如果`检查点`映射中第一个`检查点构造体`中的区块号比给入区块号大,则返回0 96 | - 其他情况:`检查点`映射中最后一个`检查点构造体`中的区块号大于给入区块号,并且`检查点数量`不为0 97 | - 则找到`检查点`映射中from区块为给入区块号的`检查点构造体` 98 | - 如果没有则返回`检查点`映射中给定区块之前最后一个有记录的`检查点构造体`中的票数 99 | ``` 100 | 参数 101 | address account //账户地址 102 | uint blockNumber //区块号 103 | ``` 104 | 1. 确认区块号小于当前区块号 105 | 2. 通过`检查点数`映射查询账户地址的`检查点数量` 106 | 3. 如果`检查点数量` == 0 返回 0(终止运行) 107 | 4. 如果`检查点`映射中账户地址最后一个记录中from块号小于等于区块号 108 | - 返回 `检查点`映射中账户地址最后一格记录中的票数(终止运行) 109 | 5. 如果`检查点`映射中账户地址第一个记录中from块号大于给入区块号,则返回0(终止运行) 110 | 6. 通过二分查找找到`检查点`映射中from区块为给入区块号的`检查点构造体`中的票数(终止运行) 111 | 7. 如果没有则返回给入区块号之前最临近区块的`检查点构造体`的`检查点索引` 112 | 8. 返回`检查点`映射中用户`索引值`为`检查点索引`的`检查点构造体`中的票数 113 | 114 | ### _delegate 更换委托人 115 | - 内部方法,由内部调用 116 | - 这个方法除了修改委托人映射以外,还要将被委托人的余额对应的票数转移给新委托人 117 | ``` 118 | 参数 119 | address delegator //被委托人 120 | address delegator //新委托人 121 | ``` 122 | 1. 获取被委托人的当前委托人 123 | 2. 获取被委托人当前的sushi余额 124 | 3. 修改委托人映射,将被委托人的委托人替换成新委托人 125 | 4. 触发委托人更改事件 126 | 5. 调用转移投票数方法,将被委托人的余额数量的票数转移到新委托人 127 | 128 | ### _moveDelegates 转移投票数 129 | - 这个方法是将源地址的`检查点`映射中的票数减去转移的票数,目标地址的`检查点`映射中的票数加上转移的票数 130 | ``` 131 | 参数 132 | address srcRep //源地址 133 | address dstRep //目标地址 134 | uint256 amount //转移的票数 135 | ``` 136 | 1. 首先确认源地址和目标地址不能一致,并且转移的数额不能为0 137 | 2. 如果源地址不为零的情况,即说明不是铸造方法 138 | - 查询源地址的`检查点数` 139 | - 判断源地址的`检查点数`如果大于0 140 | - 源地址的旧票数等于`检查点`映射中最后一个`检查点`的票数 141 | - 否则源地址的旧票等于0 142 | - 源地址新的票数等于源地址旧票数减去这次转移的票数 143 | - 写入`检查点` 144 | 3. 如果目标地址不为零的情况,即说明不是销毁方法 145 | - 查询目标地址的`检查点数` 146 | - 判断目标地址的`检查点数`如果大于0 147 | - 目标地址的旧票数等于`检查点`映射中最后一个`检查点`的票数 148 | - 否则目标地址的旧票等于0 149 | - 目标地址新的票数等于目标地址旧票数加上这次转移的票数 150 | - 写入`检查点` 151 | 152 | ### _writeCheckpoint 写入检查点 153 | - 154 | ``` 155 | 参数 156 | address delegatee //委托人地址 157 | uint32 nCheckpoints //检查点 158 | uint256 oldVotes //旧票数 159 | uint256 newVotes //新票数 160 | ``` 161 | 1. 将区块号限制在32位2进制之内(防止溢出) 162 | 2. 如果`检查点数`大于零,并且`检查点`映射委托人`最后一个`检查点中的`检查点构造体`中的from块号等于当前区块号 163 | - 修改`检查点`映射中委托人`最后一个`检查点中的`检查点构造体`中的票数为新票数 164 | 3. 否则 165 | - 定义`检查点`映射中委托人`给入的`检查点中的`检查点构造体`为新建`检查点构造体`,参数为当前区块号和新票数 166 | - 委托人`检查点数`映射中将`检查点数量`加1 -------------------------------------------------------------------------------- /UniswapModify.md: -------------------------------------------------------------------------------- 1 | # Uniswap 合约修改文档 2 | - 为了配合SushiSwap的运行,Uniswap做了一些修改 3 | 4 | ## 工厂合约 5 | - 增加迁移合约地址 6 | ``` 7 | // 迁移合约地址 8 | address public override migrator; 9 | ``` 10 | - 增加配对合约Bytecode的Hash值读取方法(原来的Uniswp没有这个方法,导致布署路由合约经常错误) 11 | ``` 12 | // 配对合约源代码Bytecode的hash值(用作前端计算配对合约地址) 13 | function pairCodeHash() external pure returns (bytes32) { 14 | return keccak256(type(UniswapV2Pair).creationCode); 15 | } 16 | ``` 17 | - 增加设置迁移合约地址的方法(迁移合约地址初始值为0地址) 18 | ``` 19 | // 设置迁移合约地址的方法,只能由feeToSetter设置 20 | function setMigrator(address _migrator) external override { 21 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 22 | migrator = _migrator; 23 | } 24 | ``` 25 | 26 | # 配对合约 27 | - 在铸造方法中增加一个判断调用铸造方法的账户是否属于迁移合约的逻辑 28 | ``` 29 | function mint(address to) external lock returns (uint liquidity) { 30 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 31 | uint balance0 = IERC20Uniswap(token0).balanceOf(address(this)); 32 | uint balance1 = IERC20Uniswap(token1).balanceOf(address(this)); 33 | uint amount0 = balance0.sub(_reserve0); 34 | uint amount1 = balance1.sub(_reserve1); 35 | 36 | bool feeOn = _mintFee(_reserve0, _reserve1); 37 | uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee 38 | if (_totalSupply == 0) { 39 | // 定义迁移合约,从工厂合约中调用迁移合约的地址 40 | address migrator = IUniswapV2Factory(factory).migrator(); 41 | // 如果调用者是迁移合约(说明是正在执行迁移操作) 42 | if (msg.sender == migrator) { 43 | // 流动性 = 迁移合约中的`需求流动性数额`,这个数额在交易开始之前是无限大,交易过程中调整为lpToken迁移到数额,交易结束之后又会被调整回无限大 44 | liquidity = IMigrator(migrator).desiredLiquidity(); 45 | // 确认流动性数额大于0并且不等于无限大 46 | require(liquidity > 0 && liquidity != uint256(-1), "Bad desired liquidity"); 47 | // 否则 48 | } else { 49 | // 确认迁移地址等于0地址(说明不在迁移过程中,属于交易所营业后的创建流动性操作) 50 | require(migrator == address(0), "Must not have migrator"); 51 | liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); 52 | _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 53 | } 54 | } else { 55 | liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); 56 | } 57 | require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); 58 | _mint(to, liquidity); 59 | 60 | _update(balance0, balance1, _reserve0, _reserve1); 61 | if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date 62 | emit Mint(msg.sender, amount0, amount1); 63 | } 64 | ``` -------------------------------------------------------------------------------- /contracts/GovernorAlpha.sol: -------------------------------------------------------------------------------- 1 | // 从COMPOUND拷贝的治理协议 2 | // COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol 3 | // 版权所有2020 Compound Labs,Inc. 4 | // Copyright 2020 Compound Labs, Inc. 5 | // 如果满足以下条件,则允许以源代码和二进制形式进行重新分发和使用,无论是否经过修改,都可以: 6 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | // 1.源代码的重新分发必须保留上述版权声明,此条件列表和以下免责声明。 8 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | // 2.以二进制形式重新分发必须在分发随附的文档和/或其他材料中复制上述版权声明,此条件列表以及以下免责声明。 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | // 3.未经事先特别书面许可,不得使用版权所有者的名称或其贡献者的名字来认可或促销从该软件衍生的产品。 12 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | // 版权持有者和贡献者按“原样”提供此软件,不提供任何明示或暗示的担保,包括但不限于针对特定目的的适销性和适用性的暗示担保。在任何情况下,版权持有人或贡献者均不对任何直接,间接,偶发,特殊,专有或后果性的损害(包括但不限于,替代商品或服务的购买,使用,数据,或业务中断),无论基于合同,严格责任或侵权行为(包括疏忽或其他方式),无论是否出于任何责任,都应通过使用本软件的任何方式(即使已事先告知)进行了赔偿。 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | // 16 | // Ctrl + f键可查看XXX的所有修改。 17 | // Ctrl+f for XXX to see all the modifications. 18 | // 为了简化和安全起见,将uint96s更改为uint256s。 19 | // uint96s are changed to uint256s for simplicity and safety. 20 | 21 | // XXX: pragma solidity ^0.5.16; 22 | pragma solidity 0.6.12; 23 | pragma experimental ABIEncoderV2; 24 | 25 | import "./SushiToken.sol"; 26 | // 治理合约,目前尚未部署 27 | contract GovernorAlpha { 28 | /// @notice 合约名称 29 | /// @notice The name of this contract 30 | // XXX:名称 = "Compound Governor Alpha"; 31 | // XXX: string public constant name = "Compound Governor Alpha"; 32 | string public constant name = "Sushi Governor Alpha"; 33 | 34 | /** 35 | * @notice 达到法定人数和投票成功所需要的支持提案的票数 36 | * @dev sushi.总供应 / 25 37 | */ 38 | /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed 39 | // XXX: function quorumVotes() public pure returns (uint) { return 400000e18; } // 400,000 = 4% of Comp 40 | function quorumVotes() public view returns (uint256) { 41 | return sushi.totalSupply() / 25; 42 | } // 4% of Supply 43 | 44 | /** 45 | * @notice 为使投票者成为提议者所需的投票数 46 | * @dev sushi.总供应 / 100 47 | */ 48 | /// @notice The number of votes required in order for a voter to become a proposer 49 | // function proposalThreshold() public pure returns (uint) { return 100000e18; } // 100,000 = 1% of Comp 50 | function proposalThreshold() public view returns (uint256) { 51 | return sushi.totalSupply() / 100; 52 | } // 1% of Supply 53 | 54 | /** 55 | * @notice 提案中可以包含的最大操作数 56 | * @dev 返回10 57 | */ 58 | /// @notice The maximum number of actions that can be included in a proposal 59 | function proposalMaxOperations() public pure returns (uint256) { 60 | return 10; 61 | } // 10 actions 62 | 63 | /** 64 | * @notice 一旦提议,投票表决可能会延迟 65 | * @dev 返回1 66 | */ 67 | /// @notice The delay before voting on a proposal may take place, once proposed 68 | function votingDelay() public pure returns (uint256) { 69 | return 1; 70 | } // 1 block 71 | 72 | /** 73 | * @notice 对提案进行投票的持续时间(以块为单位) 74 | * @dev 返回 17280 约3天(假设15秒) 75 | */ 76 | /// @notice The duration of voting on a proposal, in blocks 77 | function votingPeriod() public pure returns (uint256) { 78 | return 17280; 79 | } // ~3 days in blocks (assuming 15s blocks) 80 | 81 | /// @notice 时间锁合约地址 82 | /// @notice The address of the Compound Protocol Timelock 83 | TimelockInterface public timelock; 84 | 85 | /// @notice SushiToken 合约地址 86 | /// @notice The address of the Compound governance token 87 | // XXX: CompInterface public comp; 88 | SushiToken public sushi; 89 | 90 | /// @notice 监护人地址 91 | /// @notice The address of the Governor Guardian 92 | address public guardian; 93 | 94 | /// @notice 总提案数量 95 | /// @notice The total number of proposals 96 | uint256 public proposalCount; 97 | 98 | // 提案构造体 99 | struct Proposal { 100 | // @notice 提案的唯一id 101 | // @notice Unique id for looking up a proposal 102 | uint256 id; 103 | // @notice 提案创建者 104 | // @notice Creator of the proposal 105 | address proposer; 106 | // @notice 提案可在表决成功后设置的时间戳 107 | // @notice The timestamp that the proposal will be available for execution, set once the vote succeeds 108 | uint256 eta; 109 | // @notice 要进行调用的目标地址的有序列表 110 | // @notice the ordered list of target addresses for calls to be made 111 | address[] targets; 112 | // @notice 要传递给要进行的调用的主币数量(即msg.value)的有序列表 113 | // @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made 114 | uint256[] values; 115 | // @notice 要调用的功能签名的有序列表 116 | // @notice The ordered list of function signatures to be called 117 | string[] signatures; 118 | // @notice 要传递给每个调用方法的调用数据的有序列表 119 | // @notice The ordered list of calldata to be passed to each call 120 | bytes[] calldatas; 121 | // @notice 开始投票的区块:持有人必须在此区块之前委派投票 122 | // @notice The block at which voting begins: holders must delegate their votes prior to this block 123 | uint256 startBlock; 124 | // @notice 投票结束的区块:必须在该区块之前进行投票 125 | // @notice The block at which voting ends: votes must be cast prior to this block 126 | uint256 endBlock; 127 | // @notice 目前赞成该提案的票数 128 | // @notice Current number of votes in favor of this proposal 129 | uint256 forVotes; 130 | // @notice 目前反对该提案的票数 131 | // @notice Current number of votes in opposition to this proposal 132 | uint256 againstVotes; 133 | // @notice 标记该提案是否已被取消的标志 134 | // @notice Flag marking whether the proposal has been canceled 135 | bool canceled; 136 | // @notice 标记该提案是否已执行的标志 137 | // @notice Flag marking whether the proposal has been executed 138 | bool executed; 139 | // @notice 整个选民的选票收据 140 | // @notice Receipts of ballots for the entire set of voters 141 | mapping(address => Receipt) receipts; 142 | } 143 | 144 | /// @notice 投票者选票收据构造体 145 | /// @notice Ballot receipt record for a voter 146 | struct Receipt { 147 | // @notice 是否已投票 148 | // @notice Whether or not a vote has been cast 149 | bool hasVoted; 150 | // @notice 选民是否支持提案 151 | // @notice Whether or not the voter supports the proposal 152 | bool support; 153 | // @notice 选民所投票的票数 154 | // @notice The number of votes the voter had, which were cast 155 | uint256 votes; 156 | } 157 | 158 | /// @notice 提案可能处于的可能状态枚举 159 | /// @notice Possible states that a proposal may be in 160 | enum ProposalState { 161 | Pending, // 处理中 162 | Active, // 活跃 163 | Canceled, // 已取消 164 | Defeated, // 已失败 165 | Succeeded, // 已成功 166 | Queued, // 已排队 167 | Expired, // 已过期 168 | Executed // 已执行 169 | } 170 | 171 | /// @notice 曾经提出过的所有提案的正式记录 172 | /// @notice The official record of all proposals ever proposed 173 | mapping(uint256 => Proposal) public proposals; 174 | 175 | /// @notice 每个提案人的最新提案 176 | /// @notice The latest proposal for each proposer 177 | mapping(address => uint256) public latestProposalIds; 178 | 179 | /// @notice EIP-712的合约域hash 180 | /// @notice The EIP-712 typehash for the contract's domain 181 | bytes32 public constant DOMAIN_TYPEHASH = keccak256( 182 | "EIP712Domain(string name,uint256 chainId,address verifyingContract)" 183 | ); 184 | 185 | /// @notice EIP-712的代理人构造体的hash 186 | /// @notice The EIP-712 typehash for the ballot struct used by the contract 187 | bytes32 public constant BALLOT_TYPEHASH = keccak256( 188 | "Ballot(uint256 proposalId,bool support)" 189 | ); 190 | 191 | /// @notice 新提案事件 192 | /// @notice An event emitted when a new proposal is created 193 | event ProposalCreated( 194 | uint256 id, 195 | address proposer, 196 | address[] targets, 197 | uint256[] values, 198 | string[] signatures, 199 | bytes[] calldatas, 200 | uint256 startBlock, 201 | uint256 endBlock, 202 | string description 203 | ); 204 | 205 | /// @notice 对提案进行投票时发出的事件 206 | /// @notice An event emitted when a vote has been cast on a proposal 207 | event VoteCast( 208 | address voter, 209 | uint256 proposalId, 210 | bool support, 211 | uint256 votes 212 | ); 213 | 214 | /// @notice 取消提案后发出的事件 215 | /// @notice An event emitted when a proposal has been canceled 216 | event ProposalCanceled(uint256 id); 217 | 218 | /// @notice 当提案已在时间锁中排队时发出的事件 219 | /// @notice An event emitted when a proposal has been queued in the Timelock 220 | event ProposalQueued(uint256 id, uint256 eta); 221 | 222 | /// @notice 在时间锁中执行投标后发出的事件 223 | /// @notice An event emitted when a proposal has been executed in the Timelock 224 | event ProposalExecuted(uint256 id); 225 | 226 | /** 227 | * @dev 构造函数 228 | * @param timelock_ 时间锁合约地址 229 | * @param sushi_ SushiToken 合约地址 230 | * @param guardian_ 监护人地址 231 | */ 232 | constructor( 233 | address timelock_, 234 | address sushi_, 235 | address guardian_ 236 | ) public { 237 | timelock = TimelockInterface(timelock_); 238 | sushi = SushiToken(sushi_); 239 | guardian = guardian_; 240 | } 241 | 242 | /** 243 | * @dev 提案方法 244 | * @param targets 目标地址数组 245 | * @param values 主币数量数组 246 | * @param signatures 签名字符串数组 247 | * @param calldatas 调用数据数组 248 | * @param description 说明 249 | * @notice 将提案写入构提案造体,并推入提案数组,发起人的提案id++ 250 | */ 251 | function propose( 252 | address[] memory targets, 253 | uint256[] memory values, 254 | string[] memory signatures, 255 | bytes[] memory calldatas, 256 | string memory description 257 | ) public returns (uint256) { 258 | require( 259 | sushi.getPriorVotes(msg.sender, sub256(block.number, 1)) > 260 | proposalThreshold(), 261 | "GovernorAlpha::propose: proposer votes below proposal threshold" 262 | ); 263 | require( 264 | targets.length == values.length && 265 | targets.length == signatures.length && 266 | targets.length == calldatas.length, 267 | "GovernorAlpha::propose: proposal function information arity mismatch" 268 | ); 269 | require( 270 | targets.length != 0, 271 | "GovernorAlpha::propose: must provide actions" 272 | ); 273 | require( 274 | targets.length <= proposalMaxOperations(), 275 | "GovernorAlpha::propose: too many actions" 276 | ); 277 | 278 | uint256 latestProposalId = latestProposalIds[msg.sender]; 279 | // 如果发起人之前有过提案,则之前的提案必须已经关闭 280 | if (latestProposalId != 0) { 281 | ProposalState proposersLatestProposalState = state( 282 | latestProposalId 283 | ); 284 | require( 285 | proposersLatestProposalState != ProposalState.Active, 286 | "GovernorAlpha::propose: one live proposal per proposer, found an already active proposal" 287 | ); 288 | require( 289 | proposersLatestProposalState != ProposalState.Pending, 290 | "GovernorAlpha::propose: one live proposal per proposer, found an already pending proposal" 291 | ); 292 | } 293 | 294 | uint256 startBlock = add256(block.number, votingDelay()); 295 | uint256 endBlock = add256(startBlock, votingPeriod()); 296 | 297 | proposalCount++; 298 | Proposal memory newProposal = Proposal({ 299 | id: proposalCount, 300 | proposer: msg.sender, 301 | eta: 0, 302 | targets: targets, 303 | values: values, 304 | signatures: signatures, 305 | calldatas: calldatas, 306 | startBlock: startBlock, 307 | endBlock: endBlock, 308 | forVotes: 0, 309 | againstVotes: 0, 310 | canceled: false, 311 | executed: false 312 | }); 313 | 314 | proposals[newProposal.id] = newProposal; 315 | latestProposalIds[newProposal.proposer] = newProposal.id; 316 | 317 | emit ProposalCreated( 318 | newProposal.id, 319 | msg.sender, 320 | targets, 321 | values, 322 | signatures, 323 | calldatas, 324 | startBlock, 325 | endBlock, 326 | description 327 | ); 328 | return newProposal.id; 329 | } 330 | 331 | /** 332 | * @dev 队列方法 333 | * @param proposalId 提案ID 334 | * @notice 将已经成功的提案推入时间锁合约的执行队列中 335 | */ 336 | function queue(uint256 proposalId) public { 337 | require( 338 | state(proposalId) == ProposalState.Succeeded, 339 | "GovernorAlpha::queue: proposal can only be queued if it is succeeded" 340 | ); 341 | Proposal storage proposal = proposals[proposalId]; 342 | uint256 eta = add256(block.timestamp, timelock.delay()); 343 | for (uint256 i = 0; i < proposal.targets.length; i++) { 344 | _queueOrRevert( 345 | proposal.targets[i], 346 | proposal.values[i], 347 | proposal.signatures[i], 348 | proposal.calldatas[i], 349 | eta 350 | ); 351 | } 352 | proposal.eta = eta; 353 | emit ProposalQueued(proposalId, eta); 354 | } 355 | 356 | /** 357 | * @dev 插入时间锁队列 358 | * @param target 目标地址 359 | * @param value 主币数量 360 | * @param signature 签名 361 | * @param data 数据 362 | * @param eta 时间 363 | * @notice 将已经成功的提案推入时间锁合约的执行队列中 364 | */ 365 | function _queueOrRevert( 366 | address target, 367 | uint256 value, 368 | string memory signature, 369 | bytes memory data, 370 | uint256 eta 371 | ) internal { 372 | require( 373 | !timelock.queuedTransactions( 374 | keccak256(abi.encode(target, value, signature, data, eta)) 375 | ), 376 | "GovernorAlpha::_queueOrRevert: proposal action already queued at eta" 377 | ); 378 | timelock.queueTransaction(target, value, signature, data, eta); 379 | } 380 | 381 | /** 382 | * @dev 执行操作 383 | * @param proposalId 提案ID 384 | * @notice 将队列中的提案推入到时间锁合约的执行方法中 385 | */ 386 | function execute(uint256 proposalId) public payable { 387 | require( 388 | state(proposalId) == ProposalState.Queued, 389 | "GovernorAlpha::execute: proposal can only be executed if it is queued" 390 | ); 391 | Proposal storage proposal = proposals[proposalId]; 392 | proposal.executed = true; 393 | for (uint256 i = 0; i < proposal.targets.length; i++) { 394 | timelock.executeTransaction.value(proposal.values[i])( 395 | proposal.targets[i], 396 | proposal.values[i], 397 | proposal.signatures[i], 398 | proposal.calldatas[i], 399 | proposal.eta 400 | ); 401 | } 402 | emit ProposalExecuted(proposalId); 403 | } 404 | 405 | /** 406 | * @dev 取消提案 407 | * @param proposalId 提案ID 408 | * @notice 将提案推入到时间锁合约的取消方法中 409 | */ 410 | function cancel(uint256 proposalId) public { 411 | ProposalState state = state(proposalId); 412 | require( 413 | state != ProposalState.Executed, 414 | "GovernorAlpha::cancel: cannot cancel executed proposal" 415 | ); 416 | 417 | Proposal storage proposal = proposals[proposalId]; 418 | require( 419 | msg.sender == guardian || 420 | sushi.getPriorVotes( 421 | proposal.proposer, 422 | sub256(block.number, 1) 423 | ) < 424 | proposalThreshold(), 425 | "GovernorAlpha::cancel: proposer above threshold" 426 | ); 427 | 428 | proposal.canceled = true; 429 | for (uint256 i = 0; i < proposal.targets.length; i++) { 430 | timelock.cancelTransaction( 431 | proposal.targets[i], 432 | proposal.values[i], 433 | proposal.signatures[i], 434 | proposal.calldatas[i], 435 | proposal.eta 436 | ); 437 | } 438 | 439 | emit ProposalCanceled(proposalId); 440 | } 441 | 442 | /** 443 | * @dev 获取动作 444 | * @param proposalId 提案ID 445 | * @notice 获取提案中的动作 446 | */ 447 | function getActions(uint256 proposalId) 448 | public 449 | view 450 | returns ( 451 | address[] memory targets, 452 | uint256[] memory values, 453 | string[] memory signatures, 454 | bytes[] memory calldatas 455 | ) 456 | { 457 | Proposal storage p = proposals[proposalId]; 458 | return (p.targets, p.values, p.signatures, p.calldatas); 459 | } 460 | 461 | /** 462 | * @dev 获取收据 463 | * @param proposalId 提案ID 464 | * @param voter 投票人地址 465 | * @notice 获取提案中指定投票人的收据 466 | */ 467 | function getReceipt(uint256 proposalId, address voter) 468 | public 469 | view 470 | returns (Receipt memory) 471 | { 472 | return proposals[proposalId].receipts[voter]; 473 | } 474 | 475 | /** 476 | * @dev 提案状态 477 | * @param proposalId 提案ID 478 | * @notice 返回提案当前状态 479 | */ 480 | function state(uint256 proposalId) public view returns (ProposalState) { 481 | require( 482 | proposalCount >= proposalId && proposalId > 0, 483 | "GovernorAlpha::state: invalid proposal id" 484 | ); 485 | Proposal storage proposal = proposals[proposalId]; 486 | if (proposal.canceled) { 487 | return ProposalState.Canceled; 488 | } else if (block.number <= proposal.startBlock) { 489 | return ProposalState.Pending; 490 | } else if (block.number <= proposal.endBlock) { 491 | return ProposalState.Active; 492 | } else if ( 493 | proposal.forVotes <= proposal.againstVotes || 494 | proposal.forVotes < quorumVotes() 495 | ) { 496 | return ProposalState.Defeated; 497 | } else if (proposal.eta == 0) { 498 | return ProposalState.Succeeded; 499 | } else if (proposal.executed) { 500 | return ProposalState.Executed; 501 | } else if ( 502 | block.timestamp >= add256(proposal.eta, timelock.GRACE_PERIOD()) 503 | ) { 504 | return ProposalState.Expired; 505 | } else { 506 | return ProposalState.Queued; 507 | } 508 | } 509 | 510 | /** 511 | * @dev 投票方法 512 | * @param proposalId 提案ID 513 | * @param support 是否支持 514 | */ 515 | function castVote(uint256 proposalId, bool support) public { 516 | return _castVote(msg.sender, proposalId, support); 517 | } 518 | 519 | /** 520 | * @dev 带签名的投票方法 521 | * @param proposalId 提案ID 522 | * @param support 是否支持 523 | * @param v 签名的恢复字节 524 | * @param r ECDSA签名对的一半 525 | * @param s ECDSA签名对的一半 526 | * @notice 使用签名还原的帐号投票 527 | */ 528 | function castVoteBySig( 529 | uint256 proposalId, 530 | bool support, 531 | uint8 v, 532 | bytes32 r, 533 | bytes32 s 534 | ) public { 535 | bytes32 domainSeparator = keccak256( 536 | abi.encode( 537 | DOMAIN_TYPEHASH, 538 | keccak256(bytes(name)), 539 | getChainId(), 540 | address(this) 541 | ) 542 | ); 543 | bytes32 structHash = keccak256( 544 | abi.encode(BALLOT_TYPEHASH, proposalId, support) 545 | ); 546 | bytes32 digest = keccak256( 547 | abi.encodePacked("\x19\x01", domainSeparator, structHash) 548 | ); 549 | address signatory = ecrecover(digest, v, r, s); 550 | require( 551 | signatory != address(0), 552 | "GovernorAlpha::castVoteBySig: invalid signature" 553 | ); 554 | return _castVote(signatory, proposalId, support); 555 | } 556 | 557 | /** 558 | * @dev 投票方法 559 | * @param voter 投票人 560 | * @param proposalId 提案ID 561 | * @param support 是否支持 562 | * @notice 调用sushiToken中投票人的票数进行投票,将票数记录在收据构造体中 563 | */ 564 | function _castVote( 565 | address voter, 566 | uint256 proposalId, 567 | bool support 568 | ) internal { 569 | require( 570 | state(proposalId) == ProposalState.Active, 571 | "GovernorAlpha::_castVote: voting is closed" 572 | ); 573 | Proposal storage proposal = proposals[proposalId]; 574 | Receipt storage receipt = proposal.receipts[voter]; 575 | require( 576 | receipt.hasVoted == false, 577 | "GovernorAlpha::_castVote: voter already voted" 578 | ); 579 | uint256 votes = sushi.getPriorVotes(voter, proposal.startBlock); 580 | 581 | if (support) { 582 | proposal.forVotes = add256(proposal.forVotes, votes); 583 | } else { 584 | proposal.againstVotes = add256(proposal.againstVotes, votes); 585 | } 586 | 587 | receipt.hasVoted = true; 588 | receipt.support = support; 589 | receipt.votes = votes; 590 | 591 | emit VoteCast(voter, proposalId, support, votes); 592 | } 593 | 594 | /** 595 | * @dev 接受管理员 596 | * @notice 接受并成为时间锁合约的管理员 597 | */ 598 | function __acceptAdmin() public { 599 | require( 600 | msg.sender == guardian, 601 | "GovernorAlpha::__acceptAdmin: sender must be gov guardian" 602 | ); 603 | timelock.acceptAdmin(); 604 | } 605 | 606 | /** 607 | * @dev 放弃方法 608 | * @notice 当治理合约成为时间锁合约管理员之后,放弃管理员的权限 609 | */ 610 | function __abdicate() public { 611 | require( 612 | msg.sender == guardian, 613 | "GovernorAlpha::__abdicate: sender must be gov guardian" 614 | ); 615 | guardian = address(0); 616 | } 617 | 618 | /** 619 | * @dev 更换时间锁管理员 620 | * @param newPendingAdmin 新管理员 621 | * @param eta 执行时间 622 | * @notice 将设置时间锁管理员的操作推入时间锁合约的队列中 623 | */ 624 | function __queueSetTimelockPendingAdmin( 625 | address newPendingAdmin, 626 | uint256 eta 627 | ) public { 628 | require( 629 | msg.sender == guardian, 630 | "GovernorAlpha::__queueSetTimelockPendingAdmin: sender must be gov guardian" 631 | ); 632 | timelock.queueTransaction( 633 | address(timelock), 634 | 0, 635 | "setPendingAdmin(address)", 636 | abi.encode(newPendingAdmin), 637 | eta 638 | ); 639 | } 640 | 641 | /** 642 | * @dev 执行更换时间锁管理员 643 | * @param newPendingAdmin 新管理员 644 | * @param eta 执行时间 645 | * @notice 执行设置时间锁管理员的操作 646 | */ 647 | function __executeSetTimelockPendingAdmin( 648 | address newPendingAdmin, 649 | uint256 eta 650 | ) public { 651 | require( 652 | msg.sender == guardian, 653 | "GovernorAlpha::__executeSetTimelockPendingAdmin: sender must be gov guardian" 654 | ); 655 | timelock.executeTransaction( 656 | address(timelock), 657 | 0, 658 | "setPendingAdmin(address)", 659 | abi.encode(newPendingAdmin), 660 | eta 661 | ); 662 | } 663 | 664 | function add256(uint256 a, uint256 b) internal pure returns (uint256) { 665 | uint256 c = a + b; 666 | require(c >= a, "addition overflow"); 667 | return c; 668 | } 669 | 670 | function sub256(uint256 a, uint256 b) internal pure returns (uint256) { 671 | require(b <= a, "subtraction underflow"); 672 | return a - b; 673 | } 674 | 675 | function getChainId() internal pure returns (uint256) { 676 | uint256 chainId; 677 | assembly { 678 | chainId := chainid() 679 | } 680 | return chainId; 681 | } 682 | } 683 | 684 | interface TimelockInterface { 685 | function delay() external view returns (uint256); 686 | 687 | function GRACE_PERIOD() external view returns (uint256); 688 | 689 | function acceptAdmin() external; 690 | 691 | function queuedTransactions(bytes32 hash) external view returns (bool); 692 | 693 | function queueTransaction( 694 | address target, 695 | uint256 value, 696 | string calldata signature, 697 | bytes calldata data, 698 | uint256 eta 699 | ) external returns (bytes32); 700 | 701 | function cancelTransaction( 702 | address target, 703 | uint256 value, 704 | string calldata signature, 705 | bytes calldata data, 706 | uint256 eta 707 | ) external; 708 | 709 | function executeTransaction( 710 | address target, 711 | uint256 value, 712 | string calldata signature, 713 | bytes calldata data, 714 | uint256 eta 715 | ) external payable returns (bytes memory); 716 | } 717 | -------------------------------------------------------------------------------- /contracts/MasterChef.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity 0.6.12; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts/utils/EnumerableSet.sol"; 8 | import "@openzeppelin/contracts/math/SafeMath.sol"; 9 | import "@openzeppelin/contracts/access/Ownable.sol"; 10 | import "./SushiToken.sol"; 11 | 12 | //主厨合约地址 0xc2EdaD668740f1aA35E4D8f227fB8E17dcA888Cd 13 | 14 | // 迁移合约接口 15 | interface IMigratorChef { 16 | // 执行从旧版UniswapV2到SushiSwap的LP令牌迁移 17 | // Perform LP token migration from legacy UniswapV2 to SushiSwap. 18 | // 获取当前的LP令牌地址并返回新的LP令牌地址 19 | // Take the current LP token address and return the new LP token address. 20 | // 迁移者应该对调用者的LP令牌具有完全访问权限 21 | // Migrator should have full access to the caller's LP token. 22 | // 返回新的LP令牌地址 23 | // Return the new LP token address. 24 | // 25 | // XXX Migrator必须具有对UniswapV2 LP令牌的权限访问权限 26 | // XXX Migrator must have allowance access to UniswapV2 LP tokens. 27 | // 28 | // SushiSwap必须铸造完全相同数量的SushiSwap LP令牌,否则会发生不良情况。 29 | // 传统的UniswapV2不会这样做,所以要小心! 30 | // SushiSwap must mint EXACTLY the same amount of SushiSwap LP tokens or 31 | // else something bad will happen. Traditional UniswapV2 does not 32 | // do that so be careful! 33 | function migrate(IERC20 token) external returns (IERC20); 34 | } 35 | 36 | // MasterChef是Sushi的主人。他可以做寿司,而且他是个好人。 37 | // 38 | // 请注意,它是可拥有的,所有者拥有巨大的权力。 39 | // 一旦SUSHI得到充分分配,所有权将被转移到治理智能合约中, 40 | // 并且社区可以展示出自我治理的能力 41 | // 42 | // 祝您阅读愉快。希望它没有错误。上帝保佑。 43 | 44 | // MasterChef is the master of Sushi. He can make Sushi and he is a fair guy. 45 | // 46 | // Note that it's ownable and the owner wields tremendous power. The ownership 47 | // will be transferred to a governance smart contract once SUSHI is sufficiently 48 | // distributed and the community can show to govern itself. 49 | // 50 | // Have fun reading it. Hopefully it's bug-free. God bless. 51 | contract MasterChef is Ownable { 52 | using SafeMath for uint256; 53 | using SafeERC20 for IERC20; 54 | 55 | // 用户信息 56 | // Info of each user. 57 | struct UserInfo { 58 | uint256 amount; // How many LP tokens the user has provided.用户提供了多少个LP令牌。 59 | uint256 rewardDebt; // Reward debt. See explanation below.已奖励数额。请参阅下面的说明。 60 | // 61 | // 我们在这里做一些有趣的数学运算。基本上,在任何时间点,授予用户但待分配的SUSHI数量为: 62 | // We do some fancy math here. Basically, any point in time, the amount of SUSHIs 63 | // entitled to a user but is pending to be distributed is: 64 | // 65 | // 待处理的奖励 =(user.amount * pool.accSushiPerShare)-user.rewardDebt 66 | // pending reward = (user.amount * pool.accSushiPerShare) - user.rewardDebt 67 | // 68 | // 每当用户将lpToken存入到池子中或提取时。这是发生了什么: 69 | // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens: 70 | // 1. 池子的每股累积SUSHI(accSushiPerShare)和分配发生的最后一个块号(lastRewardBlock)被更新 71 | // 1. The pool's `accSushiPerShare` (and `lastRewardBlock`) gets updated. 72 | // 2. 用户收到待处理奖励。 73 | // 2. User receives the pending reward sent to his/her address. 74 | // 3. 用户的“amount”数额被更新 75 | // 3. User's `amount` gets updated. 76 | // 4. 用户的`rewardDebt`已奖励数额得到更新 77 | // 4. User's `rewardDebt` gets updated. 78 | } 79 | 80 | // 池子信息 81 | // Info of each pool. 82 | struct PoolInfo { 83 | IERC20 lpToken; // Address of LP token contract.LP代币合约的地址 84 | uint256 allocPoint; // How many allocation points assigned to this pool. SUSHIs to distribute per block.分配给该池的分配点数。 SUSHI按块分配 85 | uint256 lastRewardBlock; // Last block number that SUSHIs distribution occurs.SUSHIs分配发生的最后一个块号 86 | uint256 accSushiPerShare; // Accumulated SUSHIs per share, times 1e12. See below.每股累积SUSHI乘以1e12。见下文 87 | } 88 | 89 | // The SUSHI TOKEN! 90 | SushiToken public sushi; 91 | // Dev address.开发人员地址 92 | address public devaddr; 93 | // 奖励结束块号 94 | // Block number when bonus SUSHI period ends. 95 | uint256 public bonusEndBlock; 96 | // 每块创建的SUSHI令牌 97 | // SUSHI tokens created per block. 98 | uint256 public sushiPerBlock; 99 | // 早期寿司的奖金乘数 100 | // Bonus muliplier for early sushi makers. 101 | uint256 public constant BONUS_MULTIPLIER = 10; 102 | // 迁移者合同。它具有很大的力量。只能通过治理(所有者)进行设置 103 | // The migrator contract. It has a lot of power. Can only be set through governance (owner). 104 | IMigratorChef public migrator; 105 | 106 | // 池子信息数组 107 | // Info of each pool. 108 | PoolInfo[] public poolInfo; 109 | // 池子ID=>用户地址=>用户信息 的映射 110 | // Info of each user that stakes LP tokens. 111 | mapping(uint256 => mapping(address => UserInfo)) public userInfo; 112 | // 总分配点。必须是所有池中所有分配点的总和 113 | // Total allocation points. Must be the sum of all allocation points in all pools. 114 | uint256 public totalAllocPoint = 0; 115 | // SUSHI挖掘开始时的块号 116 | // The block number when SUSHI mining starts. 117 | uint256 public startBlock; 118 | 119 | event Deposit(address indexed user, uint256 indexed pid, uint256 amount); 120 | event Withdraw(address indexed user, uint256 indexed pid, uint256 amount); 121 | event EmergencyWithdraw( 122 | address indexed user, 123 | uint256 indexed pid, 124 | uint256 amount 125 | ); //紧急情况 126 | 127 | /** 128 | * @dev 构造函数 129 | * @param _sushi 寿司币地址 130 | * @param _devaddr 开发人员地址 131 | * @param _sushiPerBlock 每块创建的SUSHI令牌 132 | * @param _startBlock SUSHI挖掘开始时的块号 133 | * @param _bonusEndBlock 奖励结束块号 134 | */ 135 | // 以下是sushiswap主厨合约布署时的参数 136 | // _sushi: '0x6B3595068778DD592e39A122f4f5a5cF09C90fE2', 137 | // _devaddr: '0xF942Dba4159CB61F8AD88ca4A83f5204e8F4A6bd', 138 | // _sushiPerBlock: '100000000000000000000', 139 | // _startBlock: '10750000', 140 | // _bonusEndBlock: '10850000' 141 | constructor( 142 | SushiToken _sushi, 143 | address _devaddr, 144 | uint256 _sushiPerBlock, 145 | uint256 _startBlock, 146 | uint256 _bonusEndBlock 147 | ) public { 148 | sushi = _sushi; 149 | devaddr = _devaddr; 150 | sushiPerBlock = _sushiPerBlock; 151 | bonusEndBlock = _bonusEndBlock; 152 | startBlock = _startBlock; 153 | } 154 | 155 | /** 156 | * @dev 返回池子数量 157 | */ 158 | function poolLength() external view returns (uint256) { 159 | return poolInfo.length; 160 | } 161 | 162 | /** 163 | * @dev 将新的lp添加到池中,只能由所有者调用 164 | * @param _allocPoint 分配给该池的分配点数。 SUSHI按块分配 165 | * @param _lpToken LP代币合约的地址 166 | * @param _withUpdate 触发更新所有池的奖励变量。注意gas消耗! 167 | */ 168 | // Add a new lp to the pool. Can only be called by the owner. 169 | // XXX请勿多次添加同一LP令牌。如果您这样做,奖励将被搞砸 170 | // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do. 171 | function add( 172 | uint256 _allocPoint, 173 | IERC20 _lpToken, 174 | bool _withUpdate 175 | ) public onlyOwner { 176 | // 触发更新所有池的奖励变量 177 | if (_withUpdate) { 178 | massUpdatePools(); 179 | } 180 | // 分配发生的最后一个块号 = 当前块号 > SUSHI挖掘开始时的块号 > 当前块号 : SUSHI挖掘开始时的块号 181 | uint256 lastRewardBlock = block.number > startBlock 182 | ? block.number 183 | : startBlock; 184 | // 总分配点添加分配给该池的分配点数 185 | totalAllocPoint = totalAllocPoint.add(_allocPoint); 186 | // 池子信息推入池子数组 187 | poolInfo.push( 188 | PoolInfo({ 189 | lpToken: _lpToken, 190 | allocPoint: _allocPoint, 191 | lastRewardBlock: lastRewardBlock, 192 | accSushiPerShare: 0 193 | }) 194 | ); 195 | } 196 | 197 | /** 198 | * @dev 更新给定池的SUSHI分配点。只能由所有者调用 199 | * @param _pid 池子ID,池子数组中的索引 200 | * @param _allocPoint 新的分配给该池的分配点数。 SUSHI按块分配 201 | * @param _withUpdate 触发更新所有池的奖励变量。注意gas消耗! 202 | */ 203 | // Update the given pool's SUSHI allocation point. Can only be called by the owner. 204 | function set( 205 | uint256 _pid, 206 | uint256 _allocPoint, 207 | bool _withUpdate 208 | ) public onlyOwner { 209 | // 触发更新所有池的奖励变量 210 | if (_withUpdate) { 211 | massUpdatePools(); 212 | } 213 | // 总分配点 = 总分配点 - 池子数组[池子id].分配点数 + 新的分配给该池的分配点数 214 | totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add( 215 | _allocPoint 216 | ); 217 | // 池子数组[池子id].分配点数 = 新的分配给该池的分配点数 218 | poolInfo[_pid].allocPoint = _allocPoint; 219 | } 220 | 221 | /** 222 | * @dev 设置迁移合约地址,只能由所有者调用 223 | * @param _migrator 合约地址 224 | */ 225 | // Set the migrator contract. Can only be called by the owner. 226 | function setMigrator(IMigratorChef _migrator) public onlyOwner { 227 | migrator = _migrator; 228 | } 229 | 230 | /** 231 | * @dev 将lp令牌迁移到另一个lp合约。可以被任何人呼叫。我们相信迁移合约是正确的 232 | * @param _pid 池子id,池子数组中的索引 233 | */ 234 | // Migrate lp token to another lp contract. Can be called by anyone. We trust that migrator contract is good. 235 | function migrate(uint256 _pid) public { 236 | // 确认迁移合约已经设置 237 | require(address(migrator) != address(0), "migrate: no migrator"); 238 | // 实例化池子信息构造体 239 | PoolInfo storage pool = poolInfo[_pid]; 240 | // 实例化LP token 241 | IERC20 lpToken = pool.lpToken; 242 | // 查询LP token的余额 243 | uint256 bal = lpToken.balanceOf(address(this)); 244 | // LP token 批准迁移合约控制余额数量 245 | lpToken.safeApprove(address(migrator), bal); 246 | // 新LP token地址 = 执行迁移合约的迁移方法 247 | IERC20 newLpToken = migrator.migrate(lpToken); 248 | // 确认余额 = 新LP token中的余额 249 | require(bal == newLpToken.balanceOf(address(this)), "migrate: bad"); 250 | // 修改池子信息中的LP token地址为新LP token地址 251 | pool.lpToken = newLpToken; 252 | } 253 | 254 | /** 255 | * @dev 给出from和to的块号,返回奖励乘积 256 | * @param _from from块号 257 | * @param _to to块号 258 | * @return 奖励乘积 259 | */ 260 | // Return reward multiplier over the given _from to _to block. 261 | function getMultiplier(uint256 _from, uint256 _to) 262 | public 263 | view 264 | returns (uint256) 265 | { 266 | // 如果to块号 <= 奖励结束块号 267 | if (_to <= bonusEndBlock) { 268 | // 返回 (to块号 - from块号) * 奖金乘数 269 | return _to.sub(_from).mul(BONUS_MULTIPLIER); 270 | // 否则如果 from块号 >= 奖励结束块号 271 | } else if (_from >= bonusEndBlock) { 272 | // 返回to块号 - from块号 273 | return _to.sub(_from); 274 | // 否则 275 | } else { 276 | // 返回 (奖励结束块号 - from块号) * 奖金乘数 + (to块号 - 奖励结束块号) 277 | return 278 | bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add( 279 | _to.sub(bonusEndBlock) 280 | ); 281 | } 282 | } 283 | 284 | /** 285 | * @dev 查看功能以查看用户的处理中尚未领取的SUSHI 286 | * @param _pid 池子id 287 | * @param _user 用户地址 288 | * @return 处理中尚未领取的SUSHI数额 289 | */ 290 | // View function to see pending SUSHIs on frontend. 291 | function pendingSushi(uint256 _pid, address _user) 292 | external 293 | view 294 | returns (uint256) 295 | { 296 | // 实例化池子信息 297 | PoolInfo storage pool = poolInfo[_pid]; 298 | // 根据池子id和用户地址,实例化用户信息 299 | UserInfo storage user = userInfo[_pid][_user]; 300 | // 每股累积SUSHI 301 | uint256 accSushiPerShare = pool.accSushiPerShare; 302 | // LPtoken的供应量 = 当前合约在`池子信息.lpToken地址`的余额 303 | uint256 lpSupply = pool.lpToken.balanceOf(address(this)); 304 | // 如果当前区块号 > 池子信息.分配发生的最后一个块号 && LPtoken的供应量 != 0 305 | if (block.number > pool.lastRewardBlock && lpSupply != 0) { 306 | // 奖金乘积 = 获取奖金乘积(分配发生的最后一个块号, 当前块号) 307 | uint256 multiplier = getMultiplier( 308 | pool.lastRewardBlock, 309 | block.number 310 | ); 311 | // sushi奖励 = 奖金乘积 * 每块创建的SUSHI令牌 * 池子分配点数 / 总分配点数 312 | uint256 sushiReward = multiplier 313 | .mul(sushiPerBlock) 314 | .mul(pool.allocPoint) 315 | .div(totalAllocPoint); 316 | // 每股累积SUSHI = 每股累积SUSHI + sushi奖励 * 1e12 / LPtoken的供应量 317 | accSushiPerShare = accSushiPerShare.add( 318 | sushiReward.mul(1e12).div(lpSupply) 319 | ); 320 | } 321 | // 返回 用户.已添加的数额 * 每股累积SUSHI / 1e12 - 用户.已奖励数额 322 | return user.amount.mul(accSushiPerShare).div(1e12).sub(user.rewardDebt); 323 | } 324 | 325 | /** 326 | * @dev 更新所有池的奖励变量。注意汽油消耗 327 | */ 328 | // Update reward vairables for all pools. Be careful of gas spending! 329 | function massUpdatePools() public { 330 | // 池子数量 331 | uint256 length = poolInfo.length; 332 | // 遍历所有池子 333 | for (uint256 pid = 0; pid < length; ++pid) { 334 | // 升级池子(池子id) 335 | updatePool(pid); 336 | } 337 | } 338 | 339 | /** 340 | * @dev 将给定池的奖励变量更新为最新 341 | * @param _pid 池子id 342 | */ 343 | // Update reward variables of the given pool to be up-to-date. 344 | function updatePool(uint256 _pid) public { 345 | // 实例化池子信息 346 | PoolInfo storage pool = poolInfo[_pid]; 347 | // 如果当前区块号 <= 池子信息.分配发生的最后一个块号 348 | if (block.number <= pool.lastRewardBlock) { 349 | // 直接返回 350 | return; 351 | } 352 | // LPtoken的供应量 = 当前合约在`池子信息.lotoken地址`的余额 353 | uint256 lpSupply = pool.lpToken.balanceOf(address(this)); 354 | // 如果 LPtoken的供应量 == 0 355 | if (lpSupply == 0) { 356 | // 池子信息.分配发生的最后一个块号 = 当前块号 357 | pool.lastRewardBlock = block.number; 358 | // 返回 359 | return; 360 | } 361 | // 奖金乘积 = 获取奖金乘积(分配发生的最后一个块号, 当前块号) 362 | uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number); 363 | // sushi奖励 = 奖金乘积 * 每块创建的SUSHI令牌 * 池子分配点数 / 总分配点数 364 | uint256 sushiReward = multiplier 365 | .mul(sushiPerBlock) 366 | .mul(pool.allocPoint) 367 | .div(totalAllocPoint); 368 | // 调用sushi的铸造方法, 为管理团队铸造 (`sushi奖励` / 10) token 369 | sushi.mint(devaddr, sushiReward.div(10)); 370 | // 调用sushi的铸造方法, 为当前合约铸造 `sushi奖励` token 371 | sushi.mint(address(this), sushiReward); 372 | // 每股累积SUSHI = 每股累积SUSHI + sushi奖励 * 1e12 / LPtoken的供应量 373 | pool.accSushiPerShare = pool.accSushiPerShare.add( 374 | sushiReward.mul(1e12).div(lpSupply) 375 | ); 376 | // 池子信息.分配发生的最后一个块号 = 当前块号 377 | pool.lastRewardBlock = block.number; 378 | } 379 | 380 | /** 381 | * @dev 将LP令牌存入MasterChef进行SUSHI分配 382 | * @param _pid 池子id 383 | * @param _amount 数额 384 | */ 385 | // Deposit LP tokens to MasterChef for SUSHI allocation. 386 | function deposit(uint256 _pid, uint256 _amount) public { 387 | // 实例化池子信息 388 | PoolInfo storage pool = poolInfo[_pid]; 389 | // 根据池子id和当前用户地址,实例化用户信息 390 | UserInfo storage user = userInfo[_pid][msg.sender]; 391 | // 将给定池的奖励变量更新为最新 392 | updatePool(_pid); 393 | // 如果用户已添加的数额>0 394 | if (user.amount > 0) { 395 | // 待定数额 = 用户.已添加的数额 * 池子.每股累积SUSHI / 1e12 - 用户.已奖励数额 396 | uint256 pending = user 397 | .amount 398 | .mul(pool.accSushiPerShare) 399 | .div(1e12) 400 | .sub(user.rewardDebt); 401 | if (pending > 0) { 402 | // 向当前用户安全发送待定数额的sushi 403 | safeSushiTransfer(msg.sender, pending); 404 | } 405 | } 406 | if (_amount > 0) { 407 | // 调用池子.lptoken的安全发送方法,将_amount数额的lp token从当前用户发送到当前合约 408 | pool.lpToken.safeTransferFrom( 409 | address(msg.sender), 410 | address(this), 411 | _amount 412 | ); 413 | // 用户.已添加的数额 = 用户.已添加的数额 + _amount数额 414 | user.amount = user.amount.add(_amount); 415 | } 416 | // 用户.已奖励数额 = 用户.已添加的数额 * 池子.每股累积SUSHI / 1e12 417 | user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12); 418 | // 触发存款事件 419 | emit Deposit(msg.sender, _pid, _amount); 420 | } 421 | 422 | /** 423 | * @dev 从MasterChef提取LP令牌 424 | * @param _pid 池子id 425 | * @param _amount 数额 426 | */ 427 | // Withdraw LP tokens from MasterChef. 428 | function withdraw(uint256 _pid, uint256 _amount) public { 429 | // 实例化池子信息 430 | PoolInfo storage pool = poolInfo[_pid]; 431 | // 根据池子id和当前用户地址,实例化用户信息 432 | UserInfo storage user = userInfo[_pid][msg.sender]; 433 | // 确认用户.已添加数额 >= _amount数额 434 | require(user.amount >= _amount, "withdraw: not good"); 435 | // 将给定池的奖励变量更新为最新 436 | updatePool(_pid); 437 | // 待定数额 = 用户.已添加的数额 * 池子.每股累积SUSHI / 1e12 - 用户.已奖励数额 438 | uint256 pending = user.amount.mul(pool.accSushiPerShare).div(1e12).sub( 439 | user.rewardDebt 440 | ); 441 | if (pending > 0) { 442 | // 向当前用户安全发送待定数额的sushi 443 | safeSushiTransfer(msg.sender, pending); 444 | } 445 | if (_amount > 0) { 446 | // 用户.已添加的数额 = 用户.已添加的数额 - _amount数额 447 | user.amount = user.amount.sub(_amount); 448 | // 调用池子.lptoken的安全发送方法,将_amount数额的lp token从当前合约发送到当前用户 449 | pool.lpToken.safeTransfer(address(msg.sender), _amount); 450 | } 451 | // 用户.已奖励数额 = 用户.已添加的数额 * 池子.每股累积SUSHI / 1e12 452 | user.rewardDebt = user.amount.mul(pool.accSushiPerShare).div(1e12); 453 | // 触发提款事件 454 | emit Withdraw(msg.sender, _pid, _amount); 455 | } 456 | 457 | /** 458 | * @dev 提款而不关心奖励。仅紧急情况 459 | * @param _pid 池子id 460 | */ 461 | // Withdraw without caring about rewards. EMERGENCY ONLY. 462 | function emergencyWithdraw(uint256 _pid) public { 463 | // 实例化池子信息 464 | PoolInfo storage pool = poolInfo[_pid]; 465 | // 根据池子id和当前用户地址,实例化用户信息 466 | UserInfo storage user = userInfo[_pid][msg.sender]; 467 | uint256 amount = user.amount; 468 | // 用户.已添加数额 = 0 469 | user.amount = 0; 470 | // 用户.已奖励数额 = 0 471 | user.rewardDebt = 0; 472 | // 调用池子.lptoken的安全发送方法,将_amount数额的lp token从当前合约发送到当前用户 473 | pool.lpToken.safeTransfer(address(msg.sender), amount); 474 | // 触发紧急提款事件 475 | emit EmergencyWithdraw(msg.sender, _pid, amount); 476 | } 477 | 478 | /** 479 | * @dev 安全的寿司转移功能,以防万一舍入错误导致池中没有足够的寿司 480 | * @param _to to地址 481 | * @param _amount 数额 482 | */ 483 | // Safe sushi transfer function, just in case if rounding error causes pool to not have enough SUSHIs. 484 | function safeSushiTransfer(address _to, uint256 _amount) internal { 485 | // sushi余额 = 当前合约在sushi的余额 486 | uint256 sushiBal = sushi.balanceOf(address(this)); 487 | // 如果数额 > sushi余额 488 | if (_amount > sushiBal) { 489 | // 按照sushi余额发送sushi到to地址 490 | sushi.transfer(_to, sushiBal); 491 | } else { 492 | // 按照_amount数额发送sushi到to地址 493 | sushi.transfer(_to, _amount); 494 | } 495 | } 496 | 497 | /** 498 | * @dev 通过先前的开发者地址更新开发者地址 499 | * @param _devaddr 开发者地址 500 | */ 501 | // Update dev address by the previous dev. 502 | function dev(address _devaddr) public { 503 | // 确认当前账户是开发者地址 504 | require(msg.sender == devaddr, "dev: wut?"); 505 | // 赋值新地址 506 | devaddr = _devaddr; 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.25 <0.7.0; 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public last_completed_migration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) _; 10 | } 11 | 12 | constructor() public { 13 | owner = msg.sender; 14 | } 15 | 16 | function setCompleted(uint completed) public restricted { 17 | last_completed_migration = completed; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/Migrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./uniswapv2/interfaces/IUniswapV2Pair.sol"; 4 | import "./uniswapv2/interfaces/IUniswapV2Factory.sol"; 5 | 6 | // 迁移合约 地址 0x93ac37f13bffcfe181f2ab359cc7f67d9ae5cdfd 7 | contract Migrator { 8 | // 主厨合约地址 9 | address public chef; 10 | // 旧工厂合约地址 11 | address public oldFactory; 12 | // 新工厂合约 13 | IUniswapV2Factory public factory; 14 | // 不能早于的块号 15 | uint256 public notBeforeBlock; 16 | // 需求流动性数额 = 无限大 17 | uint256 public desiredLiquidity = uint256(-1); 18 | 19 | /** 20 | * @dev 构造函数 21 | * @param _chef 主厨合约地址 22 | * @param _oldFactory 旧工厂合约地址 23 | * @param _factory 新工厂合约 24 | * @param _notBeforeBlock 不能早于的块号 25 | */ 26 | // 部署合约的交易: https://etherscan.io/tx/0x2e93327b9a1b65c10ab20679a4b52cb657e3c617023afaefb5a04a1a0e261ac6 27 | // 构造函数的参数如下 28 | // _chef 0xc2edad668740f1aa35e4d8f227fb8e17dca888cd 29 | // _oldFactory 0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f 30 | // _factory 0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac 31 | // _notBeforeBlock 0 32 | constructor( 33 | address _chef, 34 | address _oldFactory, 35 | IUniswapV2Factory _factory, 36 | uint256 _notBeforeBlock 37 | ) public { 38 | chef = _chef; 39 | oldFactory = _oldFactory; 40 | factory = _factory; 41 | notBeforeBlock = _notBeforeBlock; 42 | } 43 | 44 | /** 45 | * @dev 迁移方法 46 | * @param orig Uniswap旧配对合约地址 47 | * @return IUniswapV2Pair SushiSwap新配对合约地址 48 | */ 49 | function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) { 50 | // 确认当前用户是主厨合约地址 51 | require(msg.sender == chef, "not from master chef"); 52 | // 确认发起交易的地址为SBF的帐号地址 53 | require(tx.origin == 0xD57581D9e42E9032e6f60422fA619b4A4574Ba79, "not from SBF"); 54 | // 确认当前块号大于不能早于的块号 55 | require(block.number >= notBeforeBlock, "too early to migrate"); 56 | // 确认配对合约的工厂合约地址 = 旧工厂合约地址 57 | require(orig.factory() == oldFactory, "not from old factory"); 58 | // 定义token0和token1 59 | address token0 = orig.token0(); 60 | address token1 = orig.token1(); 61 | // 通过工厂合约的获取配对方法获取配对合约地址 62 | IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); 63 | // 如果配对合约不存在 64 | if (pair == IUniswapV2Pair(address(0))) { 65 | // 配对合约地址 = 通过工厂合约地址创建配对合约 66 | pair = IUniswapV2Pair(factory.createPair(token0, token1)); 67 | } 68 | // 确认配对合约的总量为0 69 | require(pair.totalSupply() == 0, "pair must have no existing supply"); 70 | // 流动性数量 = 当前用户(主厨合约)在旧配对合约的余额 71 | uint256 lp = orig.balanceOf(msg.sender); 72 | // 如果流动性数量 = 0 返回配对合约地址 73 | if (lp == 0) return pair; 74 | // 需求流动性数额 = 流动性数量 75 | desiredLiquidity = lp; 76 | // 调用旧配对合约的发送方法,从主厨合约将流动性数量发送到旧配对合约 77 | orig.transferFrom(msg.sender, address(orig), lp); 78 | // 调用旧配对合约的销毁方法,将token0,token1发送到新配对合约 79 | orig.burn(address(pair)); 80 | // 调用新配对合约铸造方法给主厨合约铸造新流动性token 81 | pair.mint(msg.sender); 82 | // 需求流动性数额 = 无限大 83 | desiredLiquidity = uint256(-1); 84 | // 返回配对合约地址 85 | return pair; 86 | } 87 | } -------------------------------------------------------------------------------- /contracts/MockERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | 7 | contract MockERC20 is ERC20 { 8 | constructor( 9 | string memory name, 10 | string memory symbol, 11 | uint256 supply 12 | ) public ERC20(name, symbol) { 13 | _mint(msg.sender, supply); 14 | } 15 | } -------------------------------------------------------------------------------- /contracts/OldMigrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "./uniswapv2/interfaces/IUniswapV2Pair.sol"; 4 | import "./uniswapv2/interfaces/IUniswapV2Factory.sol"; 5 | 6 | // 旧迁移合约 地址 0x818180acb9d300ffc023be2300addb6879d94830 7 | contract Migrator { 8 | // 主厨合约地址 9 | address public chef; 10 | // 旧工厂合约地址 11 | address public oldFactory; 12 | // 新工厂合约 13 | IUniswapV2Factory public factory; 14 | 15 | // 不能早于的块号 16 | uint256 public notBeforeBlock; 17 | // 需求流动性数额 = 无限大 18 | uint256 public desiredLiquidity = uint256(-1); 19 | 20 | /** 21 | * @dev 构造函数 22 | * @param _chef 主厨合约地址 23 | * @param _oldFactory 旧工厂合约地址 24 | * @param _factory 新工厂合约 25 | * @param _notBeforeBlock 不能早于的块号 26 | */ 27 | // 部署合约的交易: https://etherscan.io/tx/0xffe48bf05ebb4883dd7b242da80c77fd3da795b369c0c85f907e08c687de5e16 28 | // 构造函数的参数如下 29 | // _chef 0xc2edad668740f1aa35e4d8f227fb8e17dca888cd 30 | // _oldFactory 0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f 31 | // _factory 0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac 32 | // _notBeforeBlock 0 33 | constructor( 34 | address _chef, 35 | address _oldFactory, 36 | IUniswapV2Factory _factory, 37 | uint256 _notBeforeBlock 38 | ) public { 39 | chef = _chef; 40 | oldFactory = _oldFactory; 41 | factory = _factory; 42 | notBeforeBlock = _notBeforeBlock; 43 | } 44 | 45 | /** 46 | * @dev 迁移方法 47 | * @param orig UniswapV2 旧配对合约地址 48 | * @return IUniswapV2Pair 配对合约地址 49 | */ 50 | function migrate(IUniswapV2Pair orig) public returns (IUniswapV2Pair) { 51 | // 确认当前用户是主厨合约地址 52 | require(msg.sender == chef, "not from master chef"); 53 | // 确认当前块号大于不能早于的块号 54 | require(block.number >= notBeforeBlock, "too early to migrate"); 55 | // 确认配对合约的工厂合约地址 = 旧工厂合约地址 56 | require(orig.factory() == oldFactory, "not from old factory"); 57 | // 定义token0和token1 58 | address token0 = orig.token0(); 59 | address token1 = orig.token1(); 60 | // 通过工厂合约的获取配对方法实例化配对合约 61 | IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); 62 | // 如果配对合约不存在 63 | if (pair == IUniswapV2Pair(address(0))) { 64 | // 配对合约地址 = 通过工厂合约地址创建配对合约 65 | pair = IUniswapV2Pair(factory.createPair(token0, token1)); 66 | } 67 | // 流动性数量 = 当前用户在旧配对合约的余额 68 | uint256 lp = orig.balanceOf(msg.sender); 69 | // 如果流动性数量 = 0 返回配对合约地址 70 | if (lp == 0) return pair; 71 | // 需求流动性数额 = 流动性数量 72 | desiredLiquidity = lp; 73 | // 调用旧配对合约的发送方法,从主厨合约将流动性数量发送到旧配对合约 74 | orig.transferFrom(msg.sender, address(orig), lp); 75 | // 调用旧配对合约的销毁方法,将token0,token1发送到新配对合约 76 | orig.burn(address(pair)); 77 | // 调用新配对合约铸造方法给主厨合约铸造新流动性token 78 | pair.mint(msg.sender); 79 | // 需求流动性数额 = 无限大 80 | desiredLiquidity = uint256(-1); 81 | // 返回配对合约地址 82 | return pair; 83 | } 84 | } -------------------------------------------------------------------------------- /contracts/SushiBar.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/math/SafeMath.sol"; 6 | 7 | // SushiBar 合约 地址:0x8798249c2E607446EfB7Ad49eC89dD1865Ff4272 8 | contract SushiBar is ERC20("SushiBar", "xSUSHI"){ 9 | using SafeMath for uint256; 10 | IERC20 public sushi; 11 | 12 | /** 13 | * @dev 构造函数 14 | * @param _sushi Sushi Token地址 15 | */ 16 | constructor(IERC20 _sushi) public { 17 | sushi = _sushi;// 0x6b3595068778dd592e39a122f4f5a5cf09c90fe2 18 | } 19 | 20 | /** 21 | * @dev 进入吧台,将自己的SushiToken发送到合约换取份额 22 | * @param _amount SushiToken数额 23 | */ 24 | // 进入吧台, 支付一些Sushi, 赚取份额 25 | // Enter the bar. Pay some SUSHIs. Earn some shares. 26 | function enter(uint256 _amount) public { 27 | // 当前合约的sushiToken余额 28 | uint256 totalSushi = sushi.balanceOf(address(this)); 29 | // 当前合约的总发行量 30 | uint256 totalShares = totalSupply(); 31 | // 如果 当前合约的总发行量 == 0 || 当前合约的总发行量 == 0 32 | if (totalShares == 0 || totalSushi == 0) { 33 | // 当前合约铸造amount数量的token给调用者 34 | _mint(msg.sender, _amount); 35 | } else { 36 | // what数额 = 数额 * 当前合约的总发行量 / 当前合约的sushiToken余额 37 | uint256 what = _amount.mul(totalShares).div(totalSushi); 38 | // 当前合约铸造what数量的token给调用者 39 | _mint(msg.sender, what); 40 | } 41 | // 将amount数量的sushi Token从调用者发送到当前合约地址 42 | sushi.transferFrom(msg.sender, address(this), _amount); 43 | } 44 | 45 | /** 46 | * @dev 离开吧台,取回自己的SushiToken 47 | * @param _share SUSHIs数额 48 | */ 49 | // Leave the bar. Claim back your SUSHIs. 50 | function leave(uint256 _share) public { 51 | // 当前合约的总发行量 52 | uint256 totalShares = totalSupply(); 53 | // what数额 = 份额 * 当前合约在sushiToken的余额 / 当前合约的总发行量 54 | uint256 what = _share.mul(sushi.balanceOf(address(this))).div(totalShares); 55 | // 为调用者销毁份额 56 | _burn(msg.sender, _share); 57 | // 将what数额的sushiToken发送到调用者账户 58 | sushi.transfer(msg.sender, what); 59 | } 60 | } -------------------------------------------------------------------------------- /contracts/SushiMaker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts/math/SafeMath.sol"; 5 | import "./uniswapv2/interfaces/IUniswapV2ERC20.sol"; 6 | import "./uniswapv2/interfaces/IUniswapV2Pair.sol"; 7 | import "./uniswapv2/interfaces/IUniswapV2Factory.sol"; 8 | // SushiMaker 合约 地址:0x54844afe358Ca98E4D09AAe869f25bfe072E1B1a 9 | contract SushiMaker { 10 | using SafeMath for uint256; 11 | //工厂合约地址 12 | IUniswapV2Factory public factory; 13 | //Sushi Bar地址 14 | address public bar; 15 | //Sushi Token地址 16 | address public sushi; 17 | //WETH地址 18 | address public weth; 19 | 20 | /** 21 | * @dev 构造函数 22 | * @param _factory 工厂合约地址 23 | * @param _bar Sushi Bar地址 24 | * @param _sushi Sushi Token地址 25 | * @param _weth WETH地址 26 | */ 27 | //以下是SushiMaker合约布署时构造函数的参数 28 | // _factory:'0xc0aee478e3658e2610c5f7a4a2e1777ce9e4f2ac', 29 | // _bar:'0x8798249c2e607446efb7ad49ec89dd1865ff4272', 30 | // _sushi:'0x6b3595068778dd592e39a122f4f5a5cf09c90fe2', 31 | // _weth:'0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' 32 | constructor( 33 | IUniswapV2Factory _factory, 34 | address _bar, 35 | address _sushi, 36 | address _weth 37 | ) public { 38 | factory = _factory; 39 | sushi = _sushi; 40 | bar = _bar; 41 | weth = _weth; 42 | } 43 | 44 | /** 45 | * @dev 将token转换为sushi Token 46 | * @param token0 token0 47 | * @param token1 token1 48 | */ 49 | function convert(address token0, address token1) public { 50 | // 至少我们尝试使前置运行变得更困难 51 | // At least we try to make front-running harder to do. 52 | // 确认合约调用者为初始调用用户 53 | require(msg.sender == tx.origin, "do not convert from contract"); 54 | // 通过token0和token1找到配对合约地址,并实例化配对合约 55 | IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token0, token1)); 56 | // 调用配对合约的transfer方法,将当前合约的余额发送到配对合约地址上 57 | pair.transfer(address(pair), pair.balanceOf(address(this))); 58 | // 调用配对合约的销毁方法,将流动性token销毁,之后配对合约将会向当前合约地址发送token0和token1 59 | pair.burn(address(this)); 60 | // 将token0和token1全部交换为WETH并发送到weth和SushiToken的配对合约上 61 | uint256 wethAmount = _toWETH(token0) + _toWETH(token1); 62 | // 将weth全部交换为SushiToken并发送到SushiBar合约上 63 | _toSUSHI(wethAmount); 64 | } 65 | 66 | /** 67 | * @dev 将token卖出转换为weth 68 | * @param token token 69 | */ 70 | function _toWETH(address token) internal returns (uint256) { 71 | // 如果token地址是Sushi Token地址 72 | if (token == sushi) { 73 | // 数额 = 当前合约地址在token地址上的余额 74 | uint256 amount = IERC20(token).balanceOf(address(this)); 75 | // 将数额从当前合约地址发送到sushiBar合约 76 | IERC20(token).transfer(bar, amount); 77 | // 返回0 78 | return 0; 79 | } 80 | // 如果token地址是WETH地址 81 | if (token == weth) { 82 | // 数额 = 当前合约地址在token地址上的余额 83 | uint256 amount = IERC20(token).balanceOf(address(this)); 84 | // 将数额从当前合约发送到sushi布署的工厂合约上的WETH和SushiToken的配对合约地址上 85 | IERC20(token).transfer(factory.getPair(weth, sushi), amount); 86 | // 返回数额 87 | return amount; 88 | } 89 | // 实例化token地址和WETH地址的配对合约 90 | IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(token, weth)); 91 | // 如果配对合约地址 == 0地址 返回0 92 | if (address(pair) == address(0)) { 93 | return 0; 94 | } 95 | // 从配对合约获取储备量0,储备量1 96 | (uint256 reserve0, uint256 reserve1, ) = pair.getReserves(); 97 | // 找到token0 98 | address token0 = pair.token0(); 99 | // 排序形成储备量In和储备量Out 100 | (uint256 reserveIn, uint256 reserveOut) = token0 == token 101 | ? (reserve0, reserve1) 102 | : (reserve1, reserve0); 103 | // 输入数额 = 当前合约地址在token地址的余额 104 | uint256 amountIn = IERC20(token).balanceOf(address(this)); 105 | // 税后输入数额 = 输入数额 * 997 106 | uint256 amountInWithFee = amountIn.mul(997); 107 | // 分子 = 税后输入数额 * 储备量Out 108 | uint256 numerator = amountInWithFee.mul(reserveOut); 109 | // 分母 = 储备量In * 1000 + 税后输入数额 110 | uint256 denominator = reserveIn.mul(1000).add(amountInWithFee); 111 | // 输出数额 = 分子 / 分母 112 | uint256 amountOut = numerator / denominator; 113 | // 排序输出数额0和输出数额1,有一个是0 114 | (uint256 amount0Out, uint256 amount1Out) = token0 == token 115 | ? (uint256(0), amountOut) 116 | : (amountOut, uint256(0)); 117 | // 将输入数额发送到配对合约 118 | IERC20(token).transfer(address(pair), amountIn); 119 | // 执行配对合约的交换方法(输出数额0,输出数额1,发送到WETH和token的配对合约上) 120 | pair.swap( 121 | amount0Out, 122 | amount1Out, 123 | factory.getPair(weth, sushi), 124 | new bytes(0) 125 | ); 126 | // 返回输出数额 127 | return amountOut; 128 | } 129 | 130 | /** 131 | * @dev 用amountIn数量的weth交换sushiToken并发送到sushiBar合约上 132 | * @param amountIn 输入数额 133 | */ 134 | function _toSUSHI(uint256 amountIn) internal { 135 | // 获取SushiToken和WETH的配对合约地址,并实例化配对合约 136 | IUniswapV2Pair pair = IUniswapV2Pair(factory.getPair(weth, sushi)); 137 | // 获取配对合约的储备量0,储备量1 138 | (uint256 reserve0, uint256 reserve1, ) = pair.getReserves(); 139 | // 找到token0 140 | address token0 = pair.token0(); 141 | // 排序生成储备量In和储备量Out 142 | (uint256 reserveIn, uint256 reserveOut) = token0 == weth 143 | ? (reserve0, reserve1) 144 | : (reserve1, reserve0); 145 | // 税后输入数额 = 输入数额 * 997 146 | uint256 amountInWithFee = amountIn.mul(997); 147 | // 分子 = 税后输入数额 * 储备量Out 148 | uint256 numerator = amountInWithFee.mul(reserveOut); 149 | // 分母 = 储备量In * 1000 + 税后输入数额 150 | uint256 denominator = reserveIn.mul(1000).add(amountInWithFee); 151 | // 输出数额 = 分子 / 分母 152 | uint256 amountOut = numerator / denominator; 153 | // 排序输出数额0和输出数额1,有一个是0 154 | (uint256 amount0Out, uint256 amount1Out) = token0 == weth 155 | ? (uint256(0), amountOut) 156 | : (amountOut, uint256(0)); 157 | // 执行配对合约的交换方法(输出数额0,输出数额1,发送到SushiBar合约上) 158 | pair.swap(amount0Out, amount1Out, bar, new bytes(0)); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /contracts/SushiToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.12; 2 | 3 | 4 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | 7 | // 具有治理功能的SushiToken 地址 0x6b3595068778dd592e39a122f4f5a5cf09c90fe2 8 | // SushiToken with Governance. 9 | contract SushiToken is ERC20("SushiToken", "SUSHI"), Ownable { 10 | /// @notice 为_to创建`_amount`令牌。只能由所有者(MasterChef)调用 11 | /// @notice Creates `_amount` token to `_to`. Must only be called by the owner (MasterChef). 12 | function mint(address _to, uint256 _amount) public onlyOwner { 13 | // ERC20的铸币方法 14 | _mint(_to, _amount); 15 | // 移动委托,将amount的数额的票数转移到to地址的委托人 16 | _moveDelegates(address(0), _delegates[_to], _amount); 17 | } 18 | 19 | // 从YAM的代码复制过来的 20 | // Copied and modified from YAM code: 21 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernanceStorage.sol 22 | // https://github.com/yam-finance/yam-protocol/blob/master/contracts/token/YAMGovernance.sol 23 | // 从COMPOUND代码复制过来的 24 | // Which is copied and modified from COMPOUND: 25 | // https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol 26 | 27 | // @notice 每个账户的委托人记录 28 | // @notice A record of each accounts delegate 29 | mapping (address => address) internal _delegates; 30 | 31 | /// @notice 一个检查点,用于标记给定块中的投票数 32 | /// @notice A checkpoint for marking number of votes from a given block 33 | struct Checkpoint { 34 | uint32 fromBlock; 35 | uint256 votes; 36 | } 37 | 38 | /// @notice 按索引记录每个帐户的选票检查点 地址=>索引=>检查点构造体 39 | /// @notice A record of votes checkpoints for each account, by index 40 | mapping (address => mapping (uint32 => Checkpoint)) public checkpoints; 41 | /// @notice 每个帐户的检查点数映射,地址=>数额 42 | /// @notice The number of checkpoints for each account 43 | mapping (address => uint32) public numCheckpoints; 44 | 45 | /// @notice EIP-712的合约域hash 46 | /// @notice The EIP-712 typehash for the contract's domain 47 | bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); 48 | 49 | /// @notice EIP-712的代理人构造体的hash 50 | /// @notice The EIP-712 typehash for the delegation struct used by the contract 51 | bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"); 52 | 53 | /// @notice 用于签名/验证签名的状态记录 54 | /// @notice A record of states for signing / validating signatures 55 | mapping (address => uint) public nonces; 56 | 57 | /// @notice 帐户更改其委托时发出的事件 58 | /// @notice An event thats emitted when an account changes its delegate 59 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 60 | 61 | /// @notice 当代表帐户的投票余额更改时发出的事件 62 | /// @notice An event thats emitted when a delegate account's vote balance changes 63 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 64 | 65 | /** 66 | * @notice 查询delegator的委托人 67 | * @notice Delegate votes from `msg.sender` to `delegatee` 68 | * @param delegator 被委托的地址 69 | */ 70 | function delegates(address delegator) 71 | external 72 | view 73 | returns (address) 74 | { 75 | // 返回委托人地址 76 | return _delegates[delegator]; 77 | } 78 | 79 | /** 80 | * @notice 转移当然用户的委托人 81 | * @notice Delegate votes from `msg.sender` to `delegatee` 82 | * @param delegatee 委托人地址 83 | */ 84 | function delegate(address delegatee) external { 85 | // 将`msg.sender` 的委托人更换为 `delegatee` 86 | return _delegate(msg.sender, delegatee); 87 | } 88 | 89 | /** 90 | * @notice 从签署人到delegatee的委托投票 91 | * @notice Delegates votes from signatory to `delegatee` 92 | * @param delegatee 委托人地址 93 | * @param nonce nonce值,匹配签名所需的合同状态 94 | * @param expiry 签名到期的时间 95 | * @param v 签名的恢复字节 96 | * @param r ECDSA签名对的一半 97 | * @param s ECDSA签名对的一半 98 | */ 99 | function delegateBySig( 100 | address delegatee, 101 | uint nonce, 102 | uint expiry, 103 | uint8 v, 104 | bytes32 r, 105 | bytes32 s 106 | ) 107 | external 108 | { 109 | // 域分割 = hash(域hash + 名字hash + chainId + 当前合约地址) 110 | bytes32 domainSeparator = keccak256( 111 | abi.encode( 112 | DOMAIN_TYPEHASH, 113 | keccak256(bytes(name())), 114 | getChainId(), 115 | address(this) 116 | ) 117 | ); 118 | 119 | // 构造体hash = hash(构造体的hash + 委托人地址 + nonce值 + 过期时间) 120 | bytes32 structHash = keccak256( 121 | abi.encode( 122 | DELEGATION_TYPEHASH, 123 | delegatee, 124 | nonce, 125 | expiry 126 | ) 127 | ); 128 | 129 | // 签名前数据 = hash(域分割 + 构造体hash) 130 | bytes32 digest = keccak256( 131 | abi.encodePacked( 132 | "\x19\x01", 133 | domainSeparator, 134 | structHash 135 | ) 136 | ); 137 | 138 | // 签署人地址 = 恢复地址方法(签名前数据,v,r,s) v,r,s就是签名,通过签名和签名前数据恢复出签名人的地址 139 | address signatory = ecrecover(digest, v, r, s); 140 | // 确认签署人地址 != 0地址 141 | require(signatory != address(0), "SUSHI::delegateBySig: invalid signature"); 142 | // 确认 nonce值 == nonce值映射[签署人]++ 143 | require(nonce == nonces[signatory]++, "SUSHI::delegateBySig: invalid nonce"); 144 | // 确认 当前时间戳 <= 过期时间 145 | require(now <= expiry, "SUSHI::delegateBySig: signature expired"); 146 | // 返回更换委托人 147 | return _delegate(signatory, delegatee); 148 | } 149 | 150 | /** 151 | * @notice 获取`account`的当前剩余票数 152 | * @notice Gets the current votes balance for `account` 153 | * @param account 账户地址 154 | * @return 剩余票数 155 | */ 156 | function getCurrentVotes(address account) 157 | external 158 | view 159 | returns (uint256) 160 | { 161 | // 检查点数 = 每个帐户的检查点数映射[账户地址] 162 | uint32 nCheckpoints = numCheckpoints[account]; 163 | // 返回 检查点 > 0 ? 选票检查点[账户地址][检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].票数 : 0 164 | return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0; 165 | } 166 | 167 | /** 168 | * @notice 确定帐户在指定区块前的投票数 169 | * @notice Determine the prior number of votes for an account as of a block number 170 | * @dev 块号必须是已完成的块,否则此功能将还原以防止出现错误信息 171 | * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. 172 | * @param account 账户地址 173 | * @param blockNumber 区块号 174 | * @return 帐户在给定区块中所拥有的票数 175 | */ 176 | function getPriorVotes(address account, uint blockNumber) 177 | external 178 | view 179 | returns (uint256) 180 | { 181 | // 确认 区块号 < 当前区块号 182 | require(blockNumber < block.number, "SUSHI::getPriorVotes: not yet determined"); 183 | 184 | // 检查点数 = 每个帐户的检查点数映射[账户地址] 185 | uint32 nCheckpoints = numCheckpoints[account]; 186 | // 如果检查点 == 0 返回 0 187 | if (nCheckpoints == 0) { 188 | return 0; 189 | } 190 | 191 | // 首先检查最近的余额 192 | // First check most recent balance 193 | // 如果 选票检查点[账户地址][检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].from块号 <= 区块号 194 | if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) { 195 | // 返回 选票检查点[账户地址][检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].票数 196 | return checkpoints[account][nCheckpoints - 1].votes; 197 | } 198 | 199 | // 下一步检查隐式零余额 200 | // Next check implicit zero balance 201 | // 如果 选票检查点[账户地址][0].from块号 > 区块号 返回 0 202 | if (checkpoints[account][0].fromBlock > blockNumber) { 203 | return 0; 204 | } 205 | 206 | // 通过二分查找找到检查点映射中from区块为给入区块号的检查点构造体中的票数 207 | // 如果没有则返回给入区块号之前最临近区块的检查点构造体的检查点数字 208 | uint32 lower = 0; //最小值0 209 | uint32 upper = nCheckpoints - 1; // 最大值(最后一个,索引从0开始,检查点数从1开始) 210 | while (upper > lower) { // 当最大值>最小值 211 | // 最大数与最小数之间的中间数 = 最大数 - (最大数 - 最小数) / 2 212 | uint32 center = upper - (upper - lower) / 2; // 防止溢出// ceil, avoiding overflow 213 | // 实例化检查点映射中用户索引值中间数对应的检查点构造体 214 | Checkpoint memory cp = checkpoints[account][center]; 215 | // 如果 中间数构造体中的开始区块号 等于 传入的区块号 216 | if (cp.fromBlock == blockNumber) { 217 | // 返回中间数构造体中的票数 218 | return cp.votes; 219 | // 否则如果 中间数构造体中的开始区块号 小于 传入的区块号 220 | } else if (cp.fromBlock < blockNumber) { 221 | // 最小值 = 中间值 222 | lower = center; 223 | // 否则 224 | } else { 225 | // 最大值 = 中间数 - 1 226 | upper = center - 1; 227 | } 228 | } 229 | // 返回检查点映射中用户索引值为检查点数字的检查点构造体的票数 230 | return checkpoints[account][lower].votes; 231 | } 232 | 233 | /** 234 | * @dev 更换委托人 235 | * @param delegator 被委托人 236 | * @param delegatee 新委托人 237 | */ 238 | function _delegate(address delegator, address delegatee) 239 | internal 240 | { 241 | // 被委托人的当前委托人 242 | address currentDelegate = _delegates[delegator]; 243 | // 获取基础SUSHI的余额(未缩放) 244 | uint256 delegatorBalance = balanceOf(delegator); // balance of underlying SUSHIs (not scaled); 245 | // 修改被委托人的委托人为新委托人 246 | _delegates[delegator] = delegatee; 247 | 248 | // 触发更换委托人事件 249 | emit DelegateChanged(delegator, currentDelegate, delegatee); 250 | 251 | // 转移委托票数 252 | _moveDelegates(currentDelegate, delegatee, delegatorBalance); 253 | } 254 | 255 | /** 256 | * @dev 转移委托票数 257 | * @param srcRep 源地址 258 | * @param dstRep 目标地址 259 | * @param amount 转移的票数 260 | */ 261 | function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal { 262 | // 如果源地址 != 目标地址 && 转移的票数 > 0 263 | if (srcRep != dstRep && amount > 0) { 264 | // 如果源地址 != 0地址 源地址不是0地址说明不是铸造方法 265 | if (srcRep != address(0)) { 266 | // 减少旧的代表 267 | // decrease old representative 268 | // 源地址的检查点数 269 | uint32 srcRepNum = numCheckpoints[srcRep]; 270 | // 旧的源地址票数 = 源地址的检查点数 > 0 ? 选票检查点[源地址][源地址的检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].票数 : 0 271 | uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0; 272 | // 新的源地址票数 = 旧的源地址票数 - 转移的票数 273 | uint256 srcRepNew = srcRepOld.sub(amount); 274 | // 写入检查点,修改委托人票数 275 | _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew); 276 | } 277 | // 如果目标地址 != 0地址 目标地址不是0地址说明不是销毁方法 278 | if (dstRep != address(0)) { 279 | // 增加新的代表 280 | // increase new representative 281 | // 目标地址检查点数 282 | uint32 dstRepNum = numCheckpoints[dstRep]; 283 | // 旧目标地址票数 = 目标地址检查点数 > 0 ? 选票检查点[目标地址][目标地址的检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].票数 : 0 284 | uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0; 285 | // 新目标地址票数 = 旧目标地址票数 + 转移的票数 286 | uint256 dstRepNew = dstRepOld.add(amount); 287 | // 写入检查点,修改委托人票数 288 | _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew); 289 | } 290 | } 291 | } 292 | 293 | /** 294 | * @dev 写入检查点 295 | * @param delegatee 委托人地址 296 | * @param nCheckpoints 检查点数 297 | * @param oldVotes 旧票数 298 | * @param newVotes 新票数 299 | */ 300 | function _writeCheckpoint( 301 | address delegatee, 302 | uint32 nCheckpoints, 303 | uint256 oldVotes, 304 | uint256 newVotes 305 | ) 306 | internal 307 | { 308 | // 区块号 = 限制在32位2进制之内(当前区块号) 309 | uint32 blockNumber = safe32(block.number, "SUSHI::_writeCheckpoint: block number exceeds 32 bits"); 310 | // 如果 检查点数 > 0 && 检查点映射[委托人][检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].from块号 == 当前区块号 311 | if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) { 312 | // 检查点映射[委托人][检查点数 - 1(最后一个,索引从0开始,检查点数从1开始)].票数 = 新票数 313 | checkpoints[delegatee][nCheckpoints - 1].votes = newVotes; 314 | } else { 315 | // 检查点映射[委托人][检查点] = 检查点构造体(当前区块号, 新票数) 316 | checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes); 317 | // 每个帐户的检查点数映射[委托人] = 检查点数 + 1 318 | numCheckpoints[delegatee] = nCheckpoints + 1; 319 | } 320 | // 触发委托人票数更改事件 321 | emit DelegateVotesChanged(delegatee, oldVotes, newVotes); 322 | } 323 | 324 | /** 325 | * @dev 安全的32位数字 326 | * @param n 输入数字 327 | * @param errorMessage 报错信息 328 | */ 329 | function safe32(uint n, string memory errorMessage) internal pure returns (uint32) { 330 | // 确认 n < 2**32 331 | require(n < 2**32, errorMessage); 332 | // 返回n 333 | return uint32(n); 334 | } 335 | 336 | /** 337 | * @dev 获取链id 338 | */ 339 | function getChainId() internal pure returns (uint) { 340 | // 定义chainId变量 341 | uint256 chainId; 342 | // 内联汇编取出chainId 343 | assembly { chainId := chainid() } 344 | // 返回chainId 345 | return chainId; 346 | } 347 | } -------------------------------------------------------------------------------- /contracts/Timelock.sol: -------------------------------------------------------------------------------- 1 | // 从COMPOUND拷贝的治理协议 2 | // COPIED FROM https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol 3 | // 版权所有2020 Compound Labs,Inc. 4 | // Copyright 2020 Compound Labs, Inc. 5 | // 如果满足以下条件,则允许以源代码和二进制形式进行重新分发和使用,无论是否经过修改,都可以: 6 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | // 1.源代码的重新分发必须保留上述版权声明,此条件列表和以下免责声明。 8 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | // 2.以二进制形式重新分发必须在分发随附的文档和/或其他材料中复制上述版权声明,此条件列表以及以下免责声明。 10 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | // 3.未经事先特别书面许可,不得使用版权所有者的名称或其贡献者的名字来认可或促销从该软件衍生的产品。 12 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | // 版权持有者和贡献者按“原样”提供此软件,不提供任何明示或暗示的担保,包括但不限于针对特定目的的适销性和适用性的暗示担保。在任何情况下,版权持有人或贡献者均不对任何直接,间接,偶发,特殊,专有或后果性的损害(包括但不限于,替代商品或服务的购买,使用,数据,或业务中断),无论基于合同,严格责任或侵权行为(包括疏忽或其他方式),无论是否出于任何责任,都应通过使用本软件的任何方式(即使已事先告知)进行了赔偿。 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | // 16 | // Ctrl + f键可查看XXX的所有修改。 17 | // Ctrl+f for XXX to see all the modifications. 18 | 19 | // XXX: pragma solidity ^0.5.16; 20 | pragma solidity 0.6.12; 21 | 22 | // XXX: import "./SafeMath.sol"; 23 | import "@openzeppelin/contracts/math/SafeMath.sol"; 24 | 25 | // 时间锁合约 地址 0x9a8541ddf3a932a9a922b607e9cf7301f1d47bd1 26 | contract Timelock { 27 | using SafeMath for uint256; 28 | 29 | event NewAdmin(address indexed newAdmin); 30 | event NewPendingAdmin(address indexed newPendingAdmin); 31 | event NewDelay(uint256 indexed newDelay); 32 | event CancelTransaction( 33 | bytes32 indexed txHash, 34 | address indexed target, 35 | uint256 value, 36 | string signature, 37 | bytes data, 38 | uint256 eta 39 | ); 40 | event ExecuteTransaction( 41 | bytes32 indexed txHash, 42 | address indexed target, 43 | uint256 value, 44 | string signature, 45 | bytes data, 46 | uint256 eta 47 | ); 48 | event QueueTransaction( 49 | bytes32 indexed txHash, 50 | address indexed target, 51 | uint256 value, 52 | string signature, 53 | bytes data, 54 | uint256 eta 55 | ); 56 | // 过期时间 14天 57 | uint256 public constant GRACE_PERIOD = 14 days; 58 | // 最小延迟 2天 59 | uint256 public constant MINIMUM_DELAY = 2 days; 60 | // 最大延迟 30天 61 | uint256 public constant MAXIMUM_DELAY = 30 days; 62 | 63 | // 管理员地址 64 | address public admin; 65 | // 处理中的管理员 66 | address public pendingAdmin; 67 | // 延迟时间 68 | uint256 public delay; 69 | // 是否初始化管理员 70 | bool public admin_initialized; 71 | 72 | // 交易队列 73 | mapping(bytes32 => bool) public queuedTransactions; 74 | 75 | /** 76 | * @dev 构造函数 77 | * @param admin_ 管理员地址 78 | * @param delay_ 延迟时间 79 | */ 80 | constructor(address admin_, uint256 delay_) public { 81 | require( 82 | delay_ >= MINIMUM_DELAY, 83 | "Timelock::constructor: Delay must exceed minimum delay." 84 | ); 85 | require( 86 | delay_ <= MAXIMUM_DELAY, 87 | "Timelock::constructor: Delay must not exceed maximum delay." 88 | ); 89 | 90 | admin = admin_; 91 | delay = delay_; 92 | admin_initialized = false; 93 | } 94 | 95 | // XXX: function() external payable { } 96 | receive() external payable {} 97 | 98 | /** 99 | * @dev 设置默认延迟时间方法 100 | * @param delay_ 延迟时间 101 | * @notice 这个方法只能由当前合约自身调用,也就是将设置延迟时间的方法推入执行队列后再执行 102 | */ 103 | function setDelay(uint256 delay_) public { 104 | require( 105 | msg.sender == address(this), 106 | "Timelock::setDelay: Call must come from Timelock." 107 | ); 108 | require( 109 | delay_ >= MINIMUM_DELAY, 110 | "Timelock::setDelay: Delay must exceed minimum delay." 111 | ); 112 | require( 113 | delay_ <= MAXIMUM_DELAY, 114 | "Timelock::setDelay: Delay must not exceed maximum delay." 115 | ); 116 | delay = delay_; 117 | 118 | emit NewDelay(delay); 119 | } 120 | 121 | /** 122 | * @dev 接受管理员方法 123 | */ 124 | function acceptAdmin() public { 125 | require( 126 | msg.sender == pendingAdmin, 127 | "Timelock::acceptAdmin: Call must come from pendingAdmin." 128 | ); 129 | admin = msg.sender; 130 | pendingAdmin = address(0); 131 | 132 | emit NewAdmin(admin); 133 | } 134 | 135 | /** 136 | * @dev 设置处理中的管理员方法 137 | * @param pendingAdmin_ 待处理的管理员 138 | * @notice 第一次执行只能通过初始化管理员设置,只有执行只能通过当前合约自身执行 139 | */ 140 | function setPendingAdmin(address pendingAdmin_) public { 141 | // allows one time setting of admin for deployment purposes 142 | if (admin_initialized) { 143 | require( 144 | msg.sender == address(this), 145 | "Timelock::setPendingAdmin: Call must come from Timelock." 146 | ); 147 | } else { 148 | require( 149 | msg.sender == admin, 150 | "Timelock::setPendingAdmin: First call must come from admin." 151 | ); 152 | admin_initialized = true; 153 | } 154 | pendingAdmin = pendingAdmin_; 155 | 156 | emit NewPendingAdmin(pendingAdmin); 157 | } 158 | 159 | /** 160 | * @dev 交易队列 161 | * @param target 目标地址数组 162 | * @param value 值数组 163 | * @param signature 签名字符串数组 164 | * @param data 调用数据数组 165 | * @param eta 延迟时间 166 | * @notice 只能通过管理员执行(治理合约) 167 | */ 168 | function queueTransaction( 169 | address target, 170 | uint256 value, 171 | string memory signature, 172 | bytes memory data, 173 | uint256 eta 174 | ) public returns (bytes32) { 175 | require( 176 | msg.sender == admin, 177 | "Timelock::queueTransaction: Call must come from admin." 178 | ); 179 | require( 180 | eta >= getBlockTimestamp().add(delay), 181 | "Timelock::queueTransaction: Estimated execution block must satisfy delay." 182 | ); 183 | 184 | bytes32 txHash = keccak256( 185 | abi.encode(target, value, signature, data, eta) 186 | ); 187 | queuedTransactions[txHash] = true; 188 | 189 | emit QueueTransaction(txHash, target, value, signature, data, eta); 190 | return txHash; 191 | } 192 | 193 | /** 194 | * @dev 取消交易 195 | * @param target 目标地址数组 196 | * @param value 值数组 197 | * @param signature 签名字符串数组 198 | * @param data 调用数据数组 199 | * @param eta 延迟时间 200 | * @notice 只能通过管理员执行(治理合约) 201 | */ 202 | function cancelTransaction( 203 | address target, 204 | uint256 value, 205 | string memory signature, 206 | bytes memory data, 207 | uint256 eta 208 | ) public { 209 | require( 210 | msg.sender == admin, 211 | "Timelock::cancelTransaction: Call must come from admin." 212 | ); 213 | 214 | bytes32 txHash = keccak256( 215 | abi.encode(target, value, signature, data, eta) 216 | ); 217 | queuedTransactions[txHash] = false; 218 | 219 | emit CancelTransaction(txHash, target, value, signature, data, eta); 220 | } 221 | 222 | /** 223 | * @dev 执行交易 224 | * @param target 目标地址数组 225 | * @param value 值数组 226 | * @param signature 签名字符串数组 227 | * @param data 调用数据数组 228 | * @param eta 延迟时间 229 | * @notice 只能通过管理员执行(治理合约) 230 | */ 231 | function executeTransaction( 232 | address target, 233 | uint256 value, 234 | string memory signature, 235 | bytes memory data, 236 | uint256 eta 237 | ) public payable returns (bytes memory) { 238 | require( 239 | msg.sender == admin, 240 | "Timelock::executeTransaction: Call must come from admin." 241 | ); 242 | 243 | bytes32 txHash = keccak256( 244 | abi.encode(target, value, signature, data, eta) 245 | ); 246 | require( 247 | queuedTransactions[txHash], 248 | "Timelock::executeTransaction: Transaction hasn't been queued." 249 | ); 250 | require( 251 | getBlockTimestamp() >= eta, 252 | "Timelock::executeTransaction: Transaction hasn't surpassed time lock." 253 | ); 254 | require( 255 | getBlockTimestamp() <= eta.add(GRACE_PERIOD), 256 | "Timelock::executeTransaction: Transaction is stale." 257 | ); 258 | 259 | queuedTransactions[txHash] = false; 260 | 261 | bytes memory callData; 262 | 263 | if (bytes(signature).length == 0) { 264 | callData = data; 265 | } else { 266 | callData = abi.encodePacked( 267 | bytes4(keccak256(bytes(signature))), 268 | data 269 | ); 270 | } 271 | 272 | // solium-disable-next-line security/no-call-value 273 | (bool success, bytes memory returnData) = target.call.value(value)( 274 | callData 275 | ); 276 | require( 277 | success, 278 | "Timelock::executeTransaction: Transaction execution reverted." 279 | ); 280 | 281 | emit ExecuteTransaction(txHash, target, value, signature, data, eta); 282 | 283 | return returnData; 284 | } 285 | 286 | function getBlockTimestamp() internal view returns (uint256) { 287 | // solium-disable-next-line security/no-block-members 288 | return block.timestamp; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /contracts/uniswapv2/LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /contracts/uniswapv2/README.md: -------------------------------------------------------------------------------- 1 | # Uniswap V2 Area 2 | 3 | Code from [Uniswap V2](https://github.com/Uniswap/uniswap-v2-core/tree/27f6354bae6685612c182c3bc7577e61bc8717e3/contracts) with the following modifications. 4 | 5 | 1. Change contract version to 0.6.12 and do the necessary patching. 6 | 2. Add `migrator` member in `UniswapV2Factory` which can be set by `feeToSetter`. 7 | 3. Allow `migrator` to specify the amount of `liquidity` during the first mint. Disallow first mint if migrator is set. 8 | 9 | To see all diffs: 10 | 11 | ``` 12 | $ git diff 4c4bf551417e3df09a25aa0dbb6941cccbbac11a . 13 | ``` -------------------------------------------------------------------------------- /contracts/uniswapv2/UniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | import './libraries/SafeMath.sol'; 4 | 5 | contract UniswapV2ERC20 { 6 | using SafeMathUniswap for uint; 7 | 8 | string public constant name = 'SushiSwap LP Token'; 9 | string public constant symbol = 'SLP'; 10 | uint8 public constant decimals = 18; 11 | uint public totalSupply; 12 | mapping(address => uint) public balanceOf; 13 | mapping(address => mapping(address => uint)) public allowance; 14 | 15 | bytes32 public DOMAIN_SEPARATOR; 16 | // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); 17 | bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; 18 | mapping(address => uint) public nonces; 19 | 20 | event Approval(address indexed owner, address indexed spender, uint value); 21 | event Transfer(address indexed from, address indexed to, uint value); 22 | 23 | constructor() public { 24 | uint chainId; 25 | assembly { 26 | chainId := chainid() 27 | } 28 | DOMAIN_SEPARATOR = keccak256( 29 | abi.encode( 30 | keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), 31 | keccak256(bytes(name)), 32 | keccak256(bytes('1')), 33 | chainId, 34 | address(this) 35 | ) 36 | ); 37 | } 38 | 39 | function _mint(address to, uint value) internal { 40 | totalSupply = totalSupply.add(value); 41 | balanceOf[to] = balanceOf[to].add(value); 42 | emit Transfer(address(0), to, value); 43 | } 44 | 45 | function _burn(address from, uint value) internal { 46 | balanceOf[from] = balanceOf[from].sub(value); 47 | totalSupply = totalSupply.sub(value); 48 | emit Transfer(from, address(0), value); 49 | } 50 | 51 | function _approve(address owner, address spender, uint value) private { 52 | allowance[owner][spender] = value; 53 | emit Approval(owner, spender, value); 54 | } 55 | 56 | function _transfer(address from, address to, uint value) private { 57 | balanceOf[from] = balanceOf[from].sub(value); 58 | balanceOf[to] = balanceOf[to].add(value); 59 | emit Transfer(from, to, value); 60 | } 61 | 62 | function approve(address spender, uint value) external returns (bool) { 63 | _approve(msg.sender, spender, value); 64 | return true; 65 | } 66 | 67 | function transfer(address to, uint value) external returns (bool) { 68 | _transfer(msg.sender, to, value); 69 | return true; 70 | } 71 | 72 | function transferFrom(address from, address to, uint value) external returns (bool) { 73 | if (allowance[from][msg.sender] != uint(-1)) { 74 | allowance[from][msg.sender] = allowance[from][msg.sender].sub(value); 75 | } 76 | _transfer(from, to, value); 77 | return true; 78 | } 79 | 80 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { 81 | require(deadline >= block.timestamp, 'UniswapV2: EXPIRED'); 82 | bytes32 digest = keccak256( 83 | abi.encodePacked( 84 | '\x19\x01', 85 | DOMAIN_SEPARATOR, 86 | keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) 87 | ) 88 | ); 89 | address recoveredAddress = ecrecover(digest, v, r, s); 90 | require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE'); 91 | _approve(owner, spender, value); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /contracts/uniswapv2/UniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | import './interfaces/IUniswapV2Factory.sol'; 4 | import './UniswapV2Pair.sol'; 5 | 6 | contract UniswapV2Factory is IUniswapV2Factory { 7 | address public override feeTo; 8 | address public override feeToSetter; 9 | // 迁移合约地址 10 | address public override migrator; 11 | 12 | mapping(address => mapping(address => address)) public override getPair; 13 | address[] public override allPairs; 14 | 15 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 16 | 17 | constructor(address _feeToSetter) public { 18 | feeToSetter = _feeToSetter; 19 | } 20 | 21 | function allPairsLength() external override view returns (uint) { 22 | return allPairs.length; 23 | } 24 | // 配对合约源代码Bytecode的hash值(用作前端计算配对合约地址) 25 | function pairCodeHash() external pure returns (bytes32) { 26 | return keccak256(type(UniswapV2Pair).creationCode); 27 | } 28 | 29 | function createPair(address tokenA, address tokenB) external override returns (address pair) { 30 | require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES'); 31 | (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); 32 | require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS'); 33 | require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient 34 | bytes memory bytecode = type(UniswapV2Pair).creationCode; 35 | bytes32 salt = keccak256(abi.encodePacked(token0, token1)); 36 | assembly { 37 | pair := create2(0, add(bytecode, 32), mload(bytecode), salt) 38 | } 39 | UniswapV2Pair(pair).initialize(token0, token1); 40 | getPair[token0][token1] = pair; 41 | getPair[token1][token0] = pair; // populate mapping in the reverse direction 42 | allPairs.push(pair); 43 | emit PairCreated(token0, token1, pair, allPairs.length); 44 | } 45 | 46 | function setFeeTo(address _feeTo) external override { 47 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 48 | feeTo = _feeTo; 49 | } 50 | // 设置迁移合约地址的方法,只能由feeToSetter设置 51 | function setMigrator(address _migrator) external override { 52 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 53 | migrator = _migrator; 54 | } 55 | 56 | function setFeeToSetter(address _feeToSetter) external override { 57 | require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN'); 58 | feeToSetter = _feeToSetter; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /contracts/uniswapv2/UniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | import './UniswapV2ERC20.sol'; 4 | import './libraries/Math.sol'; 5 | import './libraries/UQ112x112.sol'; 6 | import './interfaces/IERC20.sol'; 7 | import './interfaces/IUniswapV2Factory.sol'; 8 | import './interfaces/IUniswapV2Callee.sol'; 9 | 10 | 11 | interface IMigrator { 12 | // Return the desired amount of liquidity token that the migrator wants. 13 | function desiredLiquidity() external view returns (uint256); 14 | } 15 | 16 | contract UniswapV2Pair is UniswapV2ERC20 { 17 | using SafeMathUniswap for uint; 18 | using UQ112x112 for uint224; 19 | 20 | uint public constant MINIMUM_LIQUIDITY = 10**3; 21 | bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); 22 | 23 | address public factory; 24 | address public token0; 25 | address public token1; 26 | 27 | uint112 private reserve0; // uses single storage slot, accessible via getReserves 28 | uint112 private reserve1; // uses single storage slot, accessible via getReserves 29 | uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves 30 | 31 | uint public price0CumulativeLast; 32 | uint public price1CumulativeLast; 33 | uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event 34 | 35 | uint private unlocked = 1; 36 | modifier lock() { 37 | require(unlocked == 1, 'UniswapV2: LOCKED'); 38 | unlocked = 0; 39 | _; 40 | unlocked = 1; 41 | } 42 | 43 | function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) { 44 | _reserve0 = reserve0; 45 | _reserve1 = reserve1; 46 | _blockTimestampLast = blockTimestampLast; 47 | } 48 | 49 | function _safeTransfer(address token, address to, uint value) private { 50 | (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); 51 | require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); 52 | } 53 | 54 | event Mint(address indexed sender, uint amount0, uint amount1); 55 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 56 | event Swap( 57 | address indexed sender, 58 | uint amount0In, 59 | uint amount1In, 60 | uint amount0Out, 61 | uint amount1Out, 62 | address indexed to 63 | ); 64 | event Sync(uint112 reserve0, uint112 reserve1); 65 | 66 | constructor() public { 67 | factory = msg.sender; 68 | } 69 | 70 | // called once by the factory at time of deployment 71 | function initialize(address _token0, address _token1) external { 72 | require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check 73 | token0 = _token0; 74 | token1 = _token1; 75 | } 76 | 77 | // update reserves and, on the first call per block, price accumulators 78 | function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { 79 | require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); 80 | uint32 blockTimestamp = uint32(block.timestamp % 2**32); 81 | uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired 82 | if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { 83 | // * never overflows, and + overflow is desired 84 | price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; 85 | price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; 86 | } 87 | reserve0 = uint112(balance0); 88 | reserve1 = uint112(balance1); 89 | blockTimestampLast = blockTimestamp; 90 | emit Sync(reserve0, reserve1); 91 | } 92 | 93 | // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) 94 | function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { 95 | address feeTo = IUniswapV2Factory(factory).feeTo(); 96 | feeOn = feeTo != address(0); 97 | uint _kLast = kLast; // gas savings 98 | if (feeOn) { 99 | if (_kLast != 0) { 100 | uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); 101 | uint rootKLast = Math.sqrt(_kLast); 102 | if (rootK > rootKLast) { 103 | uint numerator = totalSupply.mul(rootK.sub(rootKLast)); 104 | uint denominator = rootK.mul(5).add(rootKLast); 105 | uint liquidity = numerator / denominator; 106 | if (liquidity > 0) _mint(feeTo, liquidity); 107 | } 108 | } 109 | } else if (_kLast != 0) { 110 | kLast = 0; 111 | } 112 | } 113 | 114 | // this low-level function should be called from a contract which performs important safety checks 115 | function mint(address to) external lock returns (uint liquidity) { 116 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 117 | uint balance0 = IERC20Uniswap(token0).balanceOf(address(this)); 118 | uint balance1 = IERC20Uniswap(token1).balanceOf(address(this)); 119 | uint amount0 = balance0.sub(_reserve0); 120 | uint amount1 = balance1.sub(_reserve1); 121 | 122 | bool feeOn = _mintFee(_reserve0, _reserve1); 123 | uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee 124 | if (_totalSupply == 0) { 125 | // 定义迁移合约,从工厂合约中调用迁移合约的地址 126 | address migrator = IUniswapV2Factory(factory).migrator(); 127 | // 如果调用者是迁移合约(说明是正在执行迁移操作) 128 | if (msg.sender == migrator) { 129 | // 流动性 = 迁移合约中的`需求流动性数额`,这个数额在交易开始之前是无限大,交易过程中调整为lpToken迁移到数额,交易结束之后又会被调整回无限大 130 | liquidity = IMigrator(migrator).desiredLiquidity(); 131 | // 确认流动性数额大于0并且不等于无限大 132 | require(liquidity > 0 && liquidity != uint256(-1), "Bad desired liquidity"); 133 | // 否则 134 | } else { 135 | // 确认迁移地址等于0地址(说明不在迁移过程中,属于交易所营业后的创建流动性操作) 136 | require(migrator == address(0), "Must not have migrator"); 137 | liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); 138 | _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 139 | } 140 | } else { 141 | liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); 142 | } 143 | require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); 144 | _mint(to, liquidity); 145 | 146 | _update(balance0, balance1, _reserve0, _reserve1); 147 | if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date 148 | emit Mint(msg.sender, amount0, amount1); 149 | } 150 | 151 | // this low-level function should be called from a contract which performs important safety checks 152 | function burn(address to) external lock returns (uint amount0, uint amount1) { 153 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 154 | address _token0 = token0; // gas savings 155 | address _token1 = token1; // gas savings 156 | uint balance0 = IERC20Uniswap(_token0).balanceOf(address(this)); 157 | uint balance1 = IERC20Uniswap(_token1).balanceOf(address(this)); 158 | uint liquidity = balanceOf[address(this)]; 159 | 160 | bool feeOn = _mintFee(_reserve0, _reserve1); 161 | uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee 162 | amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution 163 | amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution 164 | require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); 165 | _burn(address(this), liquidity); 166 | _safeTransfer(_token0, to, amount0); 167 | _safeTransfer(_token1, to, amount1); 168 | balance0 = IERC20Uniswap(_token0).balanceOf(address(this)); 169 | balance1 = IERC20Uniswap(_token1).balanceOf(address(this)); 170 | 171 | _update(balance0, balance1, _reserve0, _reserve1); 172 | if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date 173 | emit Burn(msg.sender, amount0, amount1, to); 174 | } 175 | 176 | // this low-level function should be called from a contract which performs important safety checks 177 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { 178 | require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); 179 | (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 180 | require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY'); 181 | 182 | uint balance0; 183 | uint balance1; 184 | { // scope for _token{0,1}, avoids stack too deep errors 185 | address _token0 = token0; 186 | address _token1 = token1; 187 | require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); 188 | if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens 189 | if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens 190 | if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); 191 | balance0 = IERC20Uniswap(_token0).balanceOf(address(this)); 192 | balance1 = IERC20Uniswap(_token1).balanceOf(address(this)); 193 | } 194 | uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; 195 | uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; 196 | require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); 197 | { // scope for reserve{0,1}Adjusted, avoids stack too deep errors 198 | uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); 199 | uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); 200 | require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); 201 | } 202 | 203 | _update(balance0, balance1, _reserve0, _reserve1); 204 | emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); 205 | } 206 | 207 | // force balances to match reserves 208 | function skim(address to) external lock { 209 | address _token0 = token0; // gas savings 210 | address _token1 = token1; // gas savings 211 | _safeTransfer(_token0, to, IERC20Uniswap(_token0).balanceOf(address(this)).sub(reserve0)); 212 | _safeTransfer(_token1, to, IERC20Uniswap(_token1).balanceOf(address(this)).sub(reserve1)); 213 | } 214 | 215 | // force reserves to match balances 216 | function sync() external lock { 217 | _update(IERC20Uniswap(token0).balanceOf(address(this)), IERC20Uniswap(token1).balanceOf(address(this)), reserve0, reserve1); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /contracts/uniswapv2/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IERC20Uniswap { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external view returns (string memory); 8 | function symbol() external view returns (string memory); 9 | function decimals() external view returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | } 18 | -------------------------------------------------------------------------------- /contracts/uniswapv2/interfaces/IUniswapV2Callee.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Callee { 4 | function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external; 5 | } 6 | -------------------------------------------------------------------------------- /contracts/uniswapv2/interfaces/IUniswapV2ERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2ERC20 { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | } -------------------------------------------------------------------------------- /contracts/uniswapv2/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Factory { 4 | event PairCreated(address indexed token0, address indexed token1, address pair, uint); 5 | 6 | function feeTo() external view returns (address); 7 | function feeToSetter() external view returns (address); 8 | function migrator() external view returns (address); 9 | 10 | function getPair(address tokenA, address tokenB) external view returns (address pair); 11 | function allPairs(uint) external view returns (address pair); 12 | function allPairsLength() external view returns (uint); 13 | 14 | function createPair(address tokenA, address tokenB) external returns (address pair); 15 | 16 | function setFeeTo(address) external; 17 | function setFeeToSetter(address) external; 18 | function setMigrator(address) external; 19 | } 20 | -------------------------------------------------------------------------------- /contracts/uniswapv2/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.5.0; 2 | 3 | interface IUniswapV2Pair { 4 | event Approval(address indexed owner, address indexed spender, uint value); 5 | event Transfer(address indexed from, address indexed to, uint value); 6 | 7 | function name() external pure returns (string memory); 8 | function symbol() external pure returns (string memory); 9 | function decimals() external pure returns (uint8); 10 | function totalSupply() external view returns (uint); 11 | function balanceOf(address owner) external view returns (uint); 12 | function allowance(address owner, address spender) external view returns (uint); 13 | 14 | function approve(address spender, uint value) external returns (bool); 15 | function transfer(address to, uint value) external returns (bool); 16 | function transferFrom(address from, address to, uint value) external returns (bool); 17 | 18 | function DOMAIN_SEPARATOR() external view returns (bytes32); 19 | function PERMIT_TYPEHASH() external pure returns (bytes32); 20 | function nonces(address owner) external view returns (uint); 21 | 22 | function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; 23 | 24 | event Mint(address indexed sender, uint amount0, uint amount1); 25 | event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 26 | event Swap( 27 | address indexed sender, 28 | uint amount0In, 29 | uint amount1In, 30 | uint amount0Out, 31 | uint amount1Out, 32 | address indexed to 33 | ); 34 | event Sync(uint112 reserve0, uint112 reserve1); 35 | 36 | function MINIMUM_LIQUIDITY() external pure returns (uint); 37 | function factory() external view returns (address); 38 | function token0() external view returns (address); 39 | function token1() external view returns (address); 40 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 41 | function price0CumulativeLast() external view returns (uint); 42 | function price1CumulativeLast() external view returns (uint); 43 | function kLast() external view returns (uint); 44 | 45 | function mint(address to) external returns (uint liquidity); 46 | function burn(address to) external returns (uint amount0, uint amount1); 47 | function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; 48 | function skim(address to) external; 49 | function sync() external; 50 | 51 | function initialize(address, address) external; 52 | } -------------------------------------------------------------------------------- /contracts/uniswapv2/libraries/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | // a library for performing various math operations 4 | 5 | library Math { 6 | function min(uint x, uint y) internal pure returns (uint z) { 7 | z = x < y ? x : y; 8 | } 9 | 10 | // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method) 11 | function sqrt(uint y) internal pure returns (uint z) { 12 | if (y > 3) { 13 | z = y; 14 | uint x = y / 2 + 1; 15 | while (x < z) { 16 | z = x; 17 | x = (y / x + x) / 2; 18 | } 19 | } else if (y != 0) { 20 | z = 1; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/uniswapv2/libraries/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) 4 | 5 | library SafeMathUniswap { 6 | function add(uint x, uint y) internal pure returns (uint z) { 7 | require((z = x + y) >= x, 'ds-math-add-overflow'); 8 | } 9 | 10 | function sub(uint x, uint y) internal pure returns (uint z) { 11 | require((z = x - y) <= x, 'ds-math-sub-underflow'); 12 | } 13 | 14 | function mul(uint x, uint y) internal pure returns (uint z) { 15 | require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/uniswapv2/libraries/UQ112x112.sol: -------------------------------------------------------------------------------- 1 | pragma solidity =0.6.12; 2 | 3 | // a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format)) 4 | 5 | // range: [0, 2**112 - 1] 6 | // resolution: 1 / 2**112 7 | 8 | library UQ112x112 { 9 | uint224 constant Q112 = 2**112; 10 | 11 | // encode a uint112 as a UQ112x112 12 | function encode(uint112 y) internal pure returns (uint224 z) { 13 | z = uint224(y) * Q112; // never overflows 14 | } 15 | 16 | // divide a UQ112x112 by a uint112, returning a UQ112x112 17 | function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) { 18 | z = x / uint224(y); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_SushiCore.js: -------------------------------------------------------------------------------- 1 | const SushiToken = artifacts.require("SushiToken"); 2 | const MasterChef = artifacts.require("MasterChef"); 3 | module.exports = function(deployer,network,accounts) { 4 | // 布署SushiToken 5 | deployer.deploy(SushiToken).then((SushiTokenInstance)=>{ 6 | // 布署主厨合约 7 | return deployer.deploy(MasterChef, 8 | SushiTokenInstance.address, //sushiToken地址 9 | accounts[0], //开发人员地址 10 | '100000000000000000000', //每块创建的SUSHI令牌 11 | '10750000', //SUSHI挖掘开始时的块号 12 | '10850000' //奖励结束块号 13 | ).then(async (MasterChefInstance)=>{ 14 | //将SushiToken的Owner权限交给主厨合约 15 | await SushiTokenInstance.transferOwnership(MasterChefInstance.address); 16 | console.log(await SushiTokenInstance.owner()); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /migrations/3_deploy_Uniswap.js: -------------------------------------------------------------------------------- 1 | const UniswapV2Factory = artifacts.require("UniswapV2Factory"); 2 | const UniswapV2Router02 = artifacts.require("UniswapV2Router02"); 3 | const WETH9 = artifacts.require("WETH9"); 4 | 5 | const weth = { 6 | mainnet: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 7 | ropsten: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 8 | rinkeby: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 9 | goerli: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 10 | kovan: '0xd0A1E359811322d97991E03f863a0C30C2cF029C', 11 | ganache: '' 12 | } 13 | 14 | module.exports = function (deployer, network, accounts) { 15 | // 布署Uniswap工厂合约 16 | deployer.deploy(UniswapV2Factory, 17 | accounts[0] //feeToSetter地址 18 | ).then(async (UniswapV2FactoryInstance) => { 19 | console.log(await UniswapV2FactoryInstance.pairCodeHash()) 20 | if(network == 'ganache'){ 21 | const WETH9Instance = await deployer.deploy(WETH9); 22 | weth.ganache = WETH9Instance.address; 23 | } 24 | return deployer.deploy(UniswapV2Router02, 25 | UniswapV2FactoryInstance.address, //Uniswap工厂合约地址 26 | weth[network], //weth地址 27 | ); 28 | }); 29 | }; -------------------------------------------------------------------------------- /migrations/4_deploy_Migrator.js: -------------------------------------------------------------------------------- 1 | const UniswapV2Factory = artifacts.require("UniswapV2Factory"); 2 | const MasterChef = artifacts.require("MasterChef"); 3 | const Migrator = artifacts.require("Migrator"); 4 | 5 | const UniswapFactoryAddress = '0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f'; 6 | module.exports = async (deployer, network, accounts) => { 7 | const UniswapV2FactoryInstance = await UniswapV2Factory.deployed(); 8 | const MasterChefInstance = await MasterChef.deployed(); 9 | return deployer.deploy(Migrator, 10 | MasterChefInstance.address, //主厨合约地址 11 | UniswapFactoryAddress, //旧工厂合约地址 12 | UniswapV2FactoryInstance.address, //新工厂合约 13 | '0', //不能早于的块号 14 | ); 15 | }; -------------------------------------------------------------------------------- /migrations/5_deploy_SushiBar.js: -------------------------------------------------------------------------------- 1 | const SushiBar = artifacts.require("SushiBar"); 2 | const SushiToken = artifacts.require("SushiToken"); 3 | 4 | module.exports = async (deployer, network, accounts) => { 5 | const SushiTokenInstance = await SushiToken.deployed(); 6 | return deployer.deploy(SushiBar, 7 | SushiTokenInstance.address //SushiToken合约地址 8 | ); 9 | }; -------------------------------------------------------------------------------- /migrations/6_deploy_SushiMaker.js: -------------------------------------------------------------------------------- 1 | const SushiMaker = artifacts.require("SushiMaker"); 2 | const UniswapV2Factory = artifacts.require("UniswapV2Factory"); 3 | const SushiBar = artifacts.require("SushiBar"); 4 | const SushiToken = artifacts.require("SushiToken"); 5 | const WETH9 = artifacts.require("WETH9"); 6 | 7 | const weth = { 8 | mainnet: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 9 | ropsten: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 10 | rinkeby: '0xc778417E063141139Fce010982780140Aa0cD5Ab', 11 | goerli: '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 12 | kovan: '0xd0A1E359811322d97991E03f863a0C30C2cF029C', 13 | ganache: '' 14 | } 15 | module.exports = async (deployer, network, accounts) => { 16 | const UniswapV2FactoryInstance = await UniswapV2Factory.deployed(); 17 | const SushiBarInstance = await SushiBar.deployed(); 18 | const SushiTokenInstance = await SushiToken.deployed(); 19 | if(network == 'ganache'){ 20 | const WETH9Instance = await deployer.deploy(WETH9); 21 | weth.ganache = WETH9Instance.address; 22 | } 23 | return deployer.deploy(SushiMaker, 24 | UniswapV2FactoryInstance.address, //工厂合约地址 25 | SushiBarInstance.address, //Sushi Bar地址 26 | SushiTokenInstance.address, //Sushi Token地址 27 | weth[network], //WETH地址 28 | ).then(async (SushiMakerInstance)=>{ 29 | //设置交易手续费接收 30 | await UniswapV2FactoryInstance.setFeeTo(SushiMakerInstance.address); 31 | console.log(await UniswapV2FactoryInstance.feeTo()); 32 | }); 33 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sushiswap", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@davidqhr/ganache-cli": "^6.4.3", 8 | "@openzeppelin/contracts": "^3.2.0", 9 | "@openzeppelin/test-helpers": "^0.5.6", 10 | "@truffle/hdwallet-provider": "^1.0.44", 11 | "dotenv": "^8.2.0", 12 | "truffle": "^5.1.41", 13 | "truffle-flattener": "^1.4.4" 14 | }, 15 | "scripts": { 16 | "compile": "truffle compile", 17 | "ganache": "ganache-cli -e 1000" 18 | }, 19 | "devDependencies": { 20 | "truffle-plugin-verify": "^0.4.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/AMasterChef.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const SushiToken = artifacts.require('SushiToken'); 3 | const MasterChef = artifacts.require('MasterChef'); 4 | const MockERC20 = artifacts.require('MockERC20'); 5 | 6 | // 测试主厨合约,生成5个帐号 7 | contract('MasterChef', ([alice, bob, carol, dev, minter]) => { 8 | beforeEach(async () => {// 每次重新部署SushiToken合约,以alice身份 9 | this.sushi = await SushiToken.new({ from: alice }); 10 | }); 11 | 12 | it('验证正确的状态变量', async () => { 13 | // 部署主厨合约 14 | // 参数:(SushiToken地址,dev帐号,每块创建1000个SUSHI令牌,SUSHI挖掘在0块开始,奖励在1000块结束) 15 | // 以alice身份部署 16 | this.chef = await MasterChef.new(this.sushi.address, dev, '1000', '0', '1000', { from: alice }); 17 | // alice将SushiToken的Owner身份转移给主厨合约地址 18 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 19 | // SushiToken地址 20 | const sushi = await this.chef.sushi(); 21 | // 开发者帐号地址 22 | const devaddr = await this.chef.devaddr(); 23 | // owner地址 24 | const owner = await this.sushi.owner(); 25 | //验证 26 | assert.equal(sushi.valueOf(), this.sushi.address); 27 | assert.equal(devaddr.valueOf(), dev); 28 | assert.equal(owner.valueOf(), this.chef.address); 29 | }); 30 | 31 | it('验证开发者帐号权限', async () => { 32 | // 部署主厨合约 33 | // 参数:(SushiToken地址,dev帐号,每块创建1000个SUSHI令牌,SUSHI挖掘在0块开始,奖励在1000块结束) 34 | // 以alice身份部署 35 | this.chef = await MasterChef.new(this.sushi.address, dev, '1000', '0', '1000', { from: alice }); 36 | // 验证开发者帐号地址 37 | assert.equal((await this.chef.devaddr()).valueOf(), dev); 38 | // 错误帐号设置开发者帐号 39 | await expectRevert(this.chef.dev(bob, { from: bob }), 'dev: wut?'); 40 | // 将开发者帐号设置为bob 41 | await this.chef.dev(bob, { from: dev }); 42 | // 验证新开发者帐号 43 | assert.equal((await this.chef.devaddr()).valueOf(), bob); 44 | // 再次修改开发者帐号 45 | await this.chef.dev(alice, { from: bob }); 46 | // 验证新开发者帐号 47 | assert.equal((await this.chef.devaddr()).valueOf(), alice); 48 | }) 49 | 50 | context('With ERC/LP token added to the field', () => { 51 | beforeEach(async () => {// 每次验证之前执行 52 | // lp = 部署模拟erc20('LPToken', 'LP', '10000000000') 53 | this.lp = await MockERC20.new('LPToken', 'LP', '10000000000', { from: minter }); 54 | // 将1000个lp从minter发送到alice账户 55 | await this.lp.transfer(alice, '1000', { from: minter }); 56 | // 将1000个lp从minter发送到bob账户 57 | await this.lp.transfer(bob, '1000', { from: minter }); 58 | // 将1000个lp从minter发送到carol账户 59 | await this.lp.transfer(carol, '1000', { from: minter }); 60 | // lp2 = 部署模拟erc20('LPToken2', 'LP2', '10000000000') 61 | this.lp2 = await MockERC20.new('LPToken2', 'LP2', '10000000000', { from: minter }); 62 | // 将1000个lp2从minter发送到alice账户 63 | await this.lp2.transfer(alice, '1000', { from: minter }); 64 | // 将1000个lp2从minter发送到bob账户 65 | await this.lp2.transfer(bob, '1000', { from: minter }); 66 | // 将1000个lp2从minter发送到carol账户 67 | await this.lp2.transfer(carol, '1000', { from: minter }); 68 | }); 69 | 70 | it('验证紧急撤出', async () => { 71 | // 每块100的耕种率,从第100块开始,直到第1000块为止 72 | // 100 per block farming rate starting at block 100 with bonus until block 1000 73 | // 部署主厨合约 74 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在100块开始,奖励在1000块结束) 75 | // 以alice身份部署 76 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '100', '1000', { from: alice }); 77 | // 将新的lp添加到池中,参数:(分配给该池的分配点数100,lp地址,触发更新所有池的奖励变量) 78 | await this.chef.add('100', this.lp.address, true); 79 | // bob批准主厨合约拥有1000个token权限 80 | await this.lp.approve(this.chef.address, '1000', { from: bob }); 81 | // bob将100个LP令牌存入主厨合约进行SUSHI分配 82 | await this.chef.deposit(0, '100', { from: bob }); 83 | // 验证当前bob的lp余额为900 84 | assert.equal((await this.lp.balanceOf(bob)).valueOf(), '900'); 85 | // 调用紧急提款方法,由bob调用,取出lp 86 | await this.chef.emergencyWithdraw(0, { from: bob }); 87 | // 验证当前bob的lp余额为1000 88 | assert.equal((await this.lp.balanceOf(bob)).valueOf(), '1000'); 89 | }); 90 | 91 | it('验证耕种时间过后才可以收到SUSHIs', async () => { 92 | // 每块100的耕种率,从第100块开始,直到第1000块为止 93 | // 100 per block farming rate starting at block 100 with bonus until block 1000 94 | // 部署主厨合约 95 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在100块开始,奖励在1000块结束) 96 | // 以alice身份部署 97 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '100', '1000', { from: alice }); 98 | // 将sushi的owner转移给主厨合约 99 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 100 | // 将新的lp添加到池中,参数:(分配给该池的分配点数100,lp地址,触发更新所有池的奖励变量) 101 | await this.chef.add('100', this.lp.address, true); 102 | // bob批准主厨合约拥有1000个token权限 103 | await this.lp.approve(this.chef.address, '1000', { from: bob }); 104 | // bob将100个LP令牌存入主厨合约进行SUSHI分配 105 | await this.chef.deposit(0, '100', { from: bob }); 106 | // 时间推移到89块 107 | await time.advanceBlockTo('89'); 108 | // bob将0个LP令牌存入主厨合约进行SUSHI分配,当前块号到达90 109 | await this.chef.deposit(0, '0', { from: bob }); // block 90 110 | // 验证bob在sushi的余额为0 111 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0'); 112 | // 时间推移到94块 113 | await time.advanceBlockTo('94'); 114 | // bob将0个LP令牌存入主厨合约进行SUSHI分配,当前块号到达95 115 | await this.chef.deposit(0, '0', { from: bob }); // block 95 116 | // 验证bob在sushi的余额为0 117 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0'); 118 | // 时间推移到99块 119 | await time.advanceBlockTo('99'); 120 | // bob将0个LP令牌存入主厨合约进行SUSHI分配,当前块号到达100 121 | await this.chef.deposit(0, '0', { from: bob }); // block 100 122 | // 验证bob在sushi的余额为0 123 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0'); 124 | // 时间推移到100块 125 | await time.advanceBlockTo('100'); 126 | // bob将0个LP令牌存入主厨合约进行SUSHI分配,当前块号到达101 127 | await this.chef.deposit(0, '0', { from: bob }); // block 101 128 | // 验证bob在sushi的余额为1000 129 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '1000'); 130 | // 时间推移到104块 131 | await time.advanceBlockTo('104'); 132 | // bob将0个LP令牌存入主厨合约进行SUSHI分配,当前块号到达105 133 | await this.chef.deposit(0, '0', { from: bob }); // block 105 134 | // 验证bob在sushi的余额为5000 135 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '5000'); 136 | // 验证开发者帐号在sushi的余额为500 137 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '500'); 138 | // 验证sushi的总量为5500 139 | assert.equal((await this.sushi.totalSupply()).valueOf(), '5500'); 140 | }); 141 | 142 | it('验证如果没有人存款,则不应该分发SUSHIs', async () => { 143 | // 每块100的耕种率,从第200块开始,直到第1000块为止 144 | // 100 per block farming rate starting at block 200 with bonus until block 1000 145 | // 部署主厨合约 146 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在200块开始,奖励在1000块结束) 147 | // 以alice身份部署 148 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '200', '1000', { from: alice }); 149 | // 将sushi的owner转移给主厨合约 150 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 151 | // 将新的lp添加到池中,参数:(分配给该池的分配点数100,lp地址,触发更新所有池的奖励变量) 152 | await this.chef.add('100', this.lp.address, true); 153 | // bob批准主厨合约拥有1000个token权限 154 | await this.lp.approve(this.chef.address, '1000', { from: bob }); 155 | // 时间推移到199块 156 | await time.advanceBlockTo('199'); 157 | // 验证sushi的总量为0 158 | assert.equal((await this.sushi.totalSupply()).valueOf(), '0'); 159 | // 时间推移到204块 160 | await time.advanceBlockTo('204'); 161 | // 验证sushi的总量为0 162 | assert.equal((await this.sushi.totalSupply()).valueOf(), '0'); 163 | // 时间推移到209块 164 | await time.advanceBlockTo('209'); 165 | // bob将10个LP令牌存入主厨合约进行SUSHI分配,当前块号到达210 166 | await this.chef.deposit(0, '10', { from: bob }); // block 210 167 | // 验证sushi的总量为0 168 | assert.equal((await this.sushi.totalSupply()).valueOf(), '0'); 169 | // 验证bob在sushi的余额为0 170 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0'); 171 | // 验证开发者帐号在sushi的余额为0 172 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '0'); 173 | // 验证bob在lp的余额为990 174 | assert.equal((await this.lp.balanceOf(bob)).valueOf(), '990'); 175 | // 时间推移到219块 176 | await time.advanceBlockTo('219'); 177 | // bob将10个LP令牌从主厨合约取出,当前块号到达220 178 | await this.chef.withdraw(0, '10', { from: bob }); // block 220 179 | // 验证sushi的总量为11000 180 | assert.equal((await this.sushi.totalSupply()).valueOf(), '11000'); 181 | // 验证bob在sushi的余额为10000 182 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '10000'); 183 | // 验证开发者帐号在sushi的余额为1000 184 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '1000'); 185 | // 验证bob在lp的余额为1000 186 | assert.equal((await this.lp.balanceOf(bob)).valueOf(), '1000'); 187 | }); 188 | 189 | it('验证为每位抵押者分配正确的SUSHIs', async () => { 190 | // 每块100的耕种率,从第300块开始,直到第1000块为止 191 | // 100 per block farming rate starting at block 300 with bonus until block 1000 192 | // 部署主厨合约 193 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在300块开始,奖励在1000块结束) 194 | // 以alice身份部署 195 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '300', '1000', { from: alice }); 196 | // 将sushi的owner转移给主厨合约 197 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 198 | // 将新的lp添加到池中,参数:(分配给该池的分配点数100,lp地址,触发更新所有池的奖励变量) 199 | await this.chef.add('100', this.lp.address, true); 200 | // alice批准主厨合约拥有1000个token权限 201 | await this.lp.approve(this.chef.address, '1000', { from: alice }); 202 | // bob批准主厨合约拥有1000个token权限 203 | await this.lp.approve(this.chef.address, '1000', { from: bob }); 204 | // carol批准主厨合约拥有1000个token权限 205 | await this.lp.approve(this.chef.address, '1000', { from: carol }); 206 | // 时间推移到310块 207 | // Alice deposits 10 LPs at block 310 208 | await time.advanceBlockTo('309'); 209 | // alice将10个LP令牌存入主厨合约进行SUSHI分配,当前块号到达310 210 | await this.chef.deposit(0, '10', { from: alice }); 211 | // 时间推移到313块 212 | // Bob deposits 20 LPs at block 314 213 | await time.advanceBlockTo('313'); 214 | // bob将20个LP令牌存入主厨合约进行SUSHI分配,当前块号到达314 215 | await this.chef.deposit(0, '20', { from: bob }); 216 | // 时间推移到317块 217 | // Carol deposits 30 LPs at block 318 218 | await time.advanceBlockTo('317'); 219 | // carol将30个LP令牌存入主厨合约进行SUSHI分配,当前块号到达318 220 | await this.chef.deposit(0, '30', { from: carol }); 221 | // Alice将10个LP令牌存入主厨合约,在320块号,在这个时间点: 222 | // Alice deposits 10 more LPs at block 320. At this point: 223 | // Alice应该持有: 4*1000(4块*每块100*奖励10) + 4*1/3*1000(4块*份额占1/3*每块100*奖励10) + 2*1/6*1000(2块*份额占1/6*每块100*奖励10) = 5666 224 | // Alice should have: 4*1000 + 4*1/3*1000 + 2*1/6*1000 = 5666 225 | // 主厨合约剩余 10000 - 5666 = 4334 其他人没领取 226 | // MasterChef should have the remaining: 10000 - 5666 = 4334 227 | // 时间推移到319块 228 | await time.advanceBlockTo('319') 229 | // alice将10个LP令牌存入主厨合约进行SUSHI分配,当前块号到达320 230 | await this.chef.deposit(0, '10', { from: alice }); 231 | // 验证sushi的总量为11000 232 | assert.equal((await this.sushi.totalSupply()).valueOf(), '11000'); 233 | // 验证alice在sushi的余额为5666 234 | assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '5666'); 235 | // 验证bob在sushi的余额为0 236 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '0'); 237 | // 验证carol在sushi的余额为0 238 | assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '0'); 239 | // 验证主厨合约在sushi的余额为4334 240 | assert.equal((await this.sushi.balanceOf(this.chef.address)).valueOf(), '4334'); 241 | // 验证开发者帐号在sushi的余额为1000 242 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '1000'); 243 | // Bob在320块号提款5个lp 244 | // Bob withdraws 5 LPs at block 330. At this point: 245 | // Bob将要领取到 : 4*2/3*1000(4块*份额占2/3*每块100*奖励10) + 2*2/6*1000(2块*份额占2/6*每块100*奖励10) + 10*2/7*1000(10块*份额占2/7*每块100*奖励10) = 6190 246 | // Bob should have: 4*2/3*1000 + 2*2/6*1000 + 10*2/7*1000 = 6190 247 | // 时间推移到219块 248 | await time.advanceBlockTo('329') 249 | // Bob在320块号提款5个lp 250 | await this.chef.withdraw(0, '5', { from: bob }); 251 | // 验证sushi的总量为22000 252 | assert.equal((await this.sushi.totalSupply()).valueOf(), '22000'); 253 | // 验证alice在sushi的余额为5666 254 | assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '5666'); 255 | // 验证bob在sushi的余额为6190 256 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '6190'); 257 | // 验证carol在sushi的余额为0 258 | assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '0'); 259 | // 验证主厨合约在sushi的余额为8144 260 | assert.equal((await this.sushi.balanceOf(this.chef.address)).valueOf(), '8144'); 261 | // 验证开发者帐号在sushi的余额为2000 262 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '2000'); 263 | // Alice提款20lp在块号340 264 | // Alice withdraws 20 LPs at block 340. 265 | // Bob提款15lp在块号350 266 | // Bob withdraws 15 LPs at block 350. 267 | // Carol提款30lp在块号360 268 | // Carol withdraws 30 LPs at block 360. 269 | // 时间推移到339块 270 | await time.advanceBlockTo('339') 271 | // Alice提款20lp在块号340 272 | await this.chef.withdraw(0, '20', { from: alice }); 273 | // 时间推移到349块 274 | await time.advanceBlockTo('349') 275 | // Bob提款15lp在块号350 276 | await this.chef.withdraw(0, '15', { from: bob }); 277 | // 时间推移到359块 278 | await time.advanceBlockTo('359') 279 | // Carol提款30lp在块号360 280 | await this.chef.withdraw(0, '30', { from: carol }); 281 | // 验证sushi的总量为55000 282 | assert.equal((await this.sushi.totalSupply()).valueOf(), '55000'); 283 | // 验证开发者帐号在sushi的余额为2000 284 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '5000'); 285 | // Alice应该持有: 5666 + 10*2/7*1000(10块*份额2/7*每块100*奖励10) + 10*2/6.5*1000(10块*份额2/6.5*每块100*奖励10) = 11600 286 | // Alice should have: 5666 + 10*2/7*1000 + 10*2/6.5*1000 = 11600 287 | assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '11600'); 288 | // Bob应该持有: 6190 + 10*1.5/6.5 * 1000(10块*份额1.5/6.5*每块100*奖励10) + 10*1.5/4.5*1000(10块*份额1.5/4.5*每块100*奖励10) = 11831 289 | // Bob should have: 6190 + 10*1.5/6.5 * 1000 + 10*1.5/4.5*1000 = 11831 290 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '11831'); 291 | // Carol应该持有: 2*3/6*1000(2块*份额3/6*每块100*奖励10) + 10*3/7*1000(10块*份额3/7*每块100*奖励10) + 10*3/6.5*1000(10块*份额3/6.5*每块100*奖励10) + 10*3/4.5*1000(10块*份额3/4.5*每块100*奖励10) + 10*1000(10块*份额1*每块100*奖励10) = 26568 292 | // Carol should have: 2*3/6*1000 + 10*3/7*1000 + 10*3/6.5*1000 + 10*3/4.5*1000 + 10*1000 = 26568 293 | assert.equal((await this.sushi.balanceOf(carol)).valueOf(), '26568'); 294 | // 所有人都回到1000个lp的持有量 295 | // All of them should have 1000 LPs back. 296 | assert.equal((await this.lp.balanceOf(alice)).valueOf(), '1000'); 297 | assert.equal((await this.lp.balanceOf(bob)).valueOf(), '1000'); 298 | assert.equal((await this.lp.balanceOf(carol)).valueOf(), '1000'); 299 | }); 300 | 301 | it('验证每个池子分配到正确的SUSHI', async () => { 302 | // 每块100的耕种率,从第400块开始,直到第1000块为止 303 | // 100 per block farming rate starting at block 400 with bonus until block 1000 304 | // 部署主厨合约 305 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在400块开始,奖励在1000块结束) 306 | // 以alice身份部署 307 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '400', '1000', { from: alice }); 308 | // 将sushi的owner转移给主厨合约 309 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 310 | // alice批准主厨合约拥有1000个lp权限 311 | await this.lp.approve(this.chef.address, '1000', { from: alice }); 312 | // bob批准主厨合约拥有1000个lp2权限 313 | await this.lp2.approve(this.chef.address, '1000', { from: bob }); 314 | // 将lp添加到池中,参数:(分配给该池的分配点数10,lp地址,触发更新所有池的奖励变量) 315 | // Add first LP to the pool with allocation 1 316 | await this.chef.add('10', this.lp.address, true); 317 | // 时间推移到410块 318 | // Alice deposits 10 LPs at block 410 319 | await time.advanceBlockTo('409'); 320 | // alice将10个LP令牌存入主厨合约进行SUSHI分配,当前块号到达410 321 | await this.chef.deposit(0, '10', { from: alice }); 322 | // 时间推移到419块 323 | // Add LP2 to the pool with allocation 2 at block 420 324 | await time.advanceBlockTo('419'); 325 | // 将lp2添加到池中,参数:(分配给该池的分配点数20,lp2地址,触发更新所有池的奖励变量) 326 | await this.chef.add('20', this.lp2.address, true); 327 | // 验证Alice在lp池子处理中的sushi奖励为10000(10块*每块100*奖励10) 328 | // Alice should have 10*1000 pending reward 329 | assert.equal((await this.chef.pendingSushi(0, alice)).valueOf(), '10000'); 330 | // 时间推移到424块 331 | // Bob deposits 10 LP2s at block 425 332 | await time.advanceBlockTo('424'); 333 | // bob将5个LP2令牌存入主厨合约进行SUSHI分配,当前块号到达425 334 | await this.chef.deposit(1, '5', { from: bob }); 335 | // 验证Alice在lp池子处理中的sushi奖励为10000+ 5*1/3*1000(5块*池子份额1/3*每块100*奖励10) = 11666 336 | // Alice should have 10000 + 5*1/3*1000 = 11666 pending reward 337 | assert.equal((await this.chef.pendingSushi(0, alice)).valueOf(), '11666'); 338 | // 时间推移到430块 339 | await time.advanceBlockTo('430'); 340 | // 在块号430,Bob应该获得: 5*2/3*1000(5块*池子份额2/3*每块100*奖励10) = 3333. alice应该获得:1666 341 | // At block 430. Bob should get 5*2/3*1000 = 3333. Alice should get ~1666 more. 342 | assert.equal((await this.chef.pendingSushi(0, alice)).valueOf(), '13333'); 343 | assert.equal((await this.chef.pendingSushi(1, bob)).valueOf(), '3333'); 344 | }); 345 | 346 | it('验证奖励期结束后应该停止奖励SUSHIs', async () => { 347 | // 每块100的耕种率,从第500块开始,直到第1000块为止 348 | // 100 per block farming rate starting at block 500 with bonus until block 600 349 | // 部署主厨合约 350 | // 参数:(SushiToken地址,dev帐号,每块创建100个SUSHI令牌,SUSHI挖掘在500块开始,奖励在1000块结束) 351 | // 以alice身份部署 352 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '500', '600', { from: alice }); 353 | // 将sushi的owner转移给主厨合约 354 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 355 | // alice批准主厨合约拥有1000个token权限 356 | await this.lp.approve(this.chef.address, '1000', { from: alice }); 357 | // 将新的lp添加到池中,参数:(分配给该池的分配点数1,lp地址,触发更新所有池的奖励变量) 358 | await this.chef.add('1', this.lp.address, true); 359 | // 时间推移到589块 360 | // Alice deposits 10 LPs at block 590 361 | await time.advanceBlockTo('589'); 362 | // alice将10个LP令牌存入主厨合约进行SUSHI分配,当前块号到达590 363 | await this.chef.deposit(0, '10', { from: alice }); 364 | // 时间推移到589块 365 | // At block 605, she should have 1000*10 + 100*5 = 10500 pending. 366 | await time.advanceBlockTo('605'); 367 | // 在块号605, alice应该获得: 1000奖励*10块 + 100奖励*5块 = 10500 处理中 368 | assert.equal((await this.chef.pendingSushi(0, alice)).valueOf(), '10500'); 369 | // 在块号606, alice提出所有处理中的奖励,总共10600 370 | // At block 606, Alice withdraws all pending rewards and should get 10600. 371 | await this.chef.deposit(0, '0', { from: alice }); 372 | // 验证alice处理中的奖励为0 373 | assert.equal((await this.chef.pendingSushi(0, alice)).valueOf(), '0'); 374 | // 验证alice的sushi余额为10600 375 | assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '10600'); 376 | }); 377 | }); 378 | }); 379 | -------------------------------------------------------------------------------- /test/Migrator.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const SushiToken = artifacts.require('SushiToken'); 3 | const MasterChef = artifacts.require('MasterChef'); 4 | const MockERC20 = artifacts.require('MockERC20'); 5 | const UniswapV2Pair = artifacts.require('UniswapV2Pair'); 6 | const UniswapV2Factory = artifacts.require('UniswapV2Factory'); 7 | const Migrator = artifacts.require('Migrator'); 8 | 9 | contract('Migrator', ([alice, bob, dev, minter]) => { 10 | beforeEach(async () => { 11 | this.factory1 = await UniswapV2Factory.new(alice, { from: alice }); 12 | this.factory2 = await UniswapV2Factory.new(alice, { from: alice }); 13 | this.sushi = await SushiToken.new({ from: alice }); 14 | this.weth = await MockERC20.new('WETH', 'WETH', '100000000', { from: minter }); 15 | this.token = await MockERC20.new('TOKEN', 'TOKEN', '100000000', { from: minter }); 16 | this.lp1 = await UniswapV2Pair.at((await this.factory1.createPair(this.weth.address, this.token.address)).logs[0].args.pair); 17 | this.lp2 = await UniswapV2Pair.at((await this.factory2.createPair(this.weth.address, this.token.address)).logs[0].args.pair); 18 | this.chef = await MasterChef.new(this.sushi.address, dev, '1000', '0', '100000', { from: alice }); 19 | this.migrator = await Migrator.new(this.chef.address, this.factory1.address, this.factory2.address, '0'); 20 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 21 | await this.chef.add('100', this.lp1.address, true, { from: alice }); 22 | }); 23 | 24 | it('should do the migration successfully', async () => { 25 | await this.token.transfer(this.lp1.address, '10000000', { from: minter }); 26 | await this.weth.transfer(this.lp1.address, '500000', { from: minter }); 27 | await this.lp1.mint(minter); 28 | assert.equal((await this.lp1.balanceOf(minter)).valueOf(), '2235067'); 29 | // Add some fake revenue 30 | await this.token.transfer(this.lp1.address, '100000', { from: minter }); 31 | await this.weth.transfer(this.lp1.address, '5000', { from: minter }); 32 | await this.lp1.sync(); 33 | await this.lp1.approve(this.chef.address, '100000000000', { from: minter }); 34 | await this.chef.deposit('0', '2000000', { from: minter }); 35 | assert.equal((await this.lp1.balanceOf(this.chef.address)).valueOf(), '2000000'); 36 | await expectRevert(this.chef.migrate(0), 'migrate: no migrator'); 37 | await this.chef.setMigrator(this.migrator.address, { from: alice }); 38 | await expectRevert(this.chef.migrate(0), 'migrate: bad'); 39 | await this.factory2.setMigrator(this.migrator.address, { from: alice }); 40 | await this.chef.migrate(0); 41 | assert.equal((await this.lp1.balanceOf(this.chef.address)).valueOf(), '0'); 42 | assert.equal((await this.lp2.balanceOf(this.chef.address)).valueOf(), '2000000'); 43 | await this.chef.withdraw('0', '2000000', { from: minter }); 44 | await this.lp2.transfer(this.lp2.address, '2000000', { from: minter }); 45 | await this.lp2.burn(bob); 46 | assert.equal((await this.token.balanceOf(bob)).valueOf(), '9033718'); 47 | assert.equal((await this.weth.balanceOf(bob)).valueOf(), '451685'); 48 | }); 49 | 50 | it('should allow first minting from public only after migrator is gone', async () => { 51 | await this.factory2.setMigrator(this.migrator.address, { from: alice }); 52 | this.tokenx = await MockERC20.new('TOKENX', 'TOKENX', '100000000', { from: minter }); 53 | this.lpx = await UniswapV2Pair.at((await this.factory2.createPair(this.weth.address, this.tokenx.address)).logs[0].args.pair); 54 | await this.weth.transfer(this.lpx.address, '10000000', { from: minter }); 55 | await this.tokenx.transfer(this.lpx.address, '500000', { from: minter }); 56 | await expectRevert(this.lpx.mint(minter), 'Must not have migrator'); 57 | await this.factory2.setMigrator('0x0000000000000000000000000000000000000000', { from: alice }); 58 | await this.lpx.mint(minter); 59 | }); 60 | }); -------------------------------------------------------------------------------- /test/SushiBar.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert } = require('@openzeppelin/test-helpers'); 2 | const SushiToken = artifacts.require('SushiToken'); 3 | const SushiBar = artifacts.require('SushiBar'); 4 | 5 | contract('SushiToken', ([alice, bob, carol]) => { 6 | beforeEach(async () => { 7 | this.sushi = await SushiToken.new({ from: alice }); 8 | this.bar = await SushiBar.new(this.sushi.address, { from: alice }); 9 | this.sushi.mint(alice, '100', { from: alice }); 10 | this.sushi.mint(bob, '100', { from: alice }); 11 | this.sushi.mint(carol, '100', { from: alice }); 12 | }); 13 | 14 | it('should not allow enter if not enough approve', async () => { 15 | await expectRevert( 16 | this.bar.enter('100', { from: alice }), 17 | 'ERC20: transfer amount exceeds allowance', 18 | ); 19 | await this.sushi.approve(this.bar.address, '50', { from: alice }); 20 | await expectRevert( 21 | this.bar.enter('100', { from: alice }), 22 | 'ERC20: transfer amount exceeds allowance', 23 | ); 24 | await this.sushi.approve(this.bar.address, '100', { from: alice }); 25 | await this.bar.enter('100', { from: alice }); 26 | assert.equal((await this.bar.balanceOf(alice)).valueOf(), '100'); 27 | }); 28 | 29 | it('should not allow withraw more than what you have', async () => { 30 | await this.sushi.approve(this.bar.address, '100', { from: alice }); 31 | await this.bar.enter('100', { from: alice }); 32 | await expectRevert( 33 | this.bar.leave('200', { from: alice }), 34 | 'ERC20: burn amount exceeds balance', 35 | ); 36 | }); 37 | 38 | it('should work with more than one participant', async () => { 39 | await this.sushi.approve(this.bar.address, '100', { from: alice }); 40 | await this.sushi.approve(this.bar.address, '100', { from: bob }); 41 | // Alice enters and gets 20 shares. Bob enters and gets 10 shares. 42 | await this.bar.enter('20', { from: alice }); 43 | await this.bar.enter('10', { from: bob }); 44 | assert.equal((await this.bar.balanceOf(alice)).valueOf(), '20'); 45 | assert.equal((await this.bar.balanceOf(bob)).valueOf(), '10'); 46 | assert.equal((await this.sushi.balanceOf(this.bar.address)).valueOf(), '30'); 47 | // SushiBar get 20 more SUSHIs from an external source. 48 | await this.sushi.transfer(this.bar.address, '20', { from: carol }); 49 | // Alice deposits 10 more SUSHIs. She should receive 10*30/50 = 6 shares. 50 | await this.bar.enter('10', { from: alice }); 51 | assert.equal((await this.bar.balanceOf(alice)).valueOf(), '26'); 52 | assert.equal((await this.bar.balanceOf(bob)).valueOf(), '10'); 53 | // Bob withdraws 5 shares. He should receive 5*60/36 = 8 shares 54 | await this.bar.leave('5', { from: bob }); 55 | assert.equal((await this.bar.balanceOf(alice)).valueOf(), '26'); 56 | assert.equal((await this.bar.balanceOf(bob)).valueOf(), '5'); 57 | assert.equal((await this.sushi.balanceOf(this.bar.address)).valueOf(), '52'); 58 | assert.equal((await this.sushi.balanceOf(alice)).valueOf(), '70'); 59 | assert.equal((await this.sushi.balanceOf(bob)).valueOf(), '98'); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/SushiMaker.test.js: -------------------------------------------------------------------------------- 1 | const SushiToken = artifacts.require('SushiToken'); 2 | const SushiMaker = artifacts.require('SushiMaker'); 3 | const MockERC20 = artifacts.require('MockERC20'); 4 | const UniswapV2Pair = artifacts.require('UniswapV2Pair'); 5 | const UniswapV2Factory = artifacts.require('UniswapV2Factory'); 6 | 7 | contract('SushiMaker', ([alice, bar, minter]) => { 8 | beforeEach(async () => { 9 | this.factory = await UniswapV2Factory.new(alice, { from: alice }); 10 | this.sushi = await SushiToken.new({ from: alice }); 11 | await this.sushi.mint(minter, '100000000', { from: alice }); 12 | this.weth = await MockERC20.new('WETH', 'WETH', '100000000', { from: minter }); 13 | this.token1 = await MockERC20.new('TOKEN1', 'TOKEN', '100000000', { from: minter }); 14 | this.token2 = await MockERC20.new('TOKEN2', 'TOKEN2', '100000000', { from: minter }); 15 | this.maker = await SushiMaker.new(this.factory.address, bar, this.sushi.address, this.weth.address); 16 | this.sushiWETH = await UniswapV2Pair.at((await this.factory.createPair(this.weth.address, this.sushi.address)).logs[0].args.pair); 17 | this.wethToken1 = await UniswapV2Pair.at((await this.factory.createPair(this.weth.address, this.token1.address)).logs[0].args.pair); 18 | this.wethToken2 = await UniswapV2Pair.at((await this.factory.createPair(this.weth.address, this.token2.address)).logs[0].args.pair); 19 | this.token1Token2 = await UniswapV2Pair.at((await this.factory.createPair(this.token1.address, this.token2.address)).logs[0].args.pair); 20 | }); 21 | 22 | it('should make SUSHIs successfully', async () => { 23 | await this.factory.setFeeTo(this.maker.address, { from: alice }); 24 | await this.weth.transfer(this.sushiWETH.address, '10000000', { from: minter }); 25 | await this.sushi.transfer(this.sushiWETH.address, '10000000', { from: minter }); 26 | await this.sushiWETH.mint(minter); 27 | await this.weth.transfer(this.wethToken1.address, '10000000', { from: minter }); 28 | await this.token1.transfer(this.wethToken1.address, '10000000', { from: minter }); 29 | await this.wethToken1.mint(minter); 30 | await this.weth.transfer(this.wethToken2.address, '10000000', { from: minter }); 31 | await this.token2.transfer(this.wethToken2.address, '10000000', { from: minter }); 32 | await this.wethToken2.mint(minter); 33 | await this.token1.transfer(this.token1Token2.address, '10000000', { from: minter }); 34 | await this.token2.transfer(this.token1Token2.address, '10000000', { from: minter }); 35 | await this.token1Token2.mint(minter); 36 | // Fake some revenue 37 | await this.token1.transfer(this.token1Token2.address, '100000', { from: minter }); 38 | await this.token2.transfer(this.token1Token2.address, '100000', { from: minter }); 39 | await this.token1Token2.sync(); 40 | await this.token1.transfer(this.token1Token2.address, '10000000', { from: minter }); 41 | await this.token2.transfer(this.token1Token2.address, '10000000', { from: minter }); 42 | await this.token1Token2.mint(minter); 43 | // Maker should have the LP now 44 | assert.equal((await this.token1Token2.balanceOf(this.maker.address)).valueOf(), '16528'); 45 | // After calling convert, bar should have SUSHI value at ~1/6 of revenue 46 | await this.maker.convert(this.token1.address, this.token2.address); 47 | assert.equal((await this.sushi.balanceOf(bar)).valueOf(), '32965'); 48 | assert.equal((await this.token1Token2.balanceOf(this.maker.address)).valueOf(), '0'); 49 | // Should also work for SUSHI-ETH pair 50 | await this.sushi.transfer(this.sushiWETH.address, '100000', { from: minter }); 51 | await this.weth.transfer(this.sushiWETH.address, '100000', { from: minter }); 52 | await this.sushiWETH.sync(); 53 | await this.sushi.transfer(this.sushiWETH.address, '10000000', { from: minter }); 54 | await this.weth.transfer(this.sushiWETH.address, '10000000', { from: minter }); 55 | await this.sushiWETH.mint(minter); 56 | assert.equal((await this.sushiWETH.balanceOf(this.maker.address)).valueOf(), '16537'); 57 | await this.maker.convert(this.sushi.address, this.weth.address); 58 | assert.equal((await this.sushi.balanceOf(bar)).valueOf(), '66249'); 59 | assert.equal((await this.sushiWETH.balanceOf(this.maker.address)).valueOf(), '0'); 60 | }); 61 | }); -------------------------------------------------------------------------------- /test/SushiToken.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert } = require('@openzeppelin/test-helpers'); 2 | const SushiToken = artifacts.require('SushiToken'); 3 | 4 | contract('SushiToken', ([alice, bob, carol]) => { 5 | beforeEach(async () => { 6 | this.sushi = await SushiToken.new({ from: alice }); 7 | }); 8 | 9 | it('should have correct name and symbol and decimal', async () => { 10 | const name = await this.sushi.name(); 11 | const symbol = await this.sushi.symbol(); 12 | const decimals = await this.sushi.decimals(); 13 | assert.equal(name.valueOf(), 'SushiToken'); 14 | assert.equal(symbol.valueOf(), 'SUSHI'); 15 | assert.equal(decimals.valueOf(), '18'); 16 | }); 17 | 18 | it('should only allow owner to mint token', async () => { 19 | await this.sushi.mint(alice, '100', { from: alice }); 20 | await this.sushi.mint(bob, '1000', { from: alice }); 21 | await expectRevert( 22 | this.sushi.mint(carol, '1000', { from: bob }), 23 | 'Ownable: caller is not the owner', 24 | ); 25 | const totalSupply = await this.sushi.totalSupply(); 26 | const aliceBal = await this.sushi.balanceOf(alice); 27 | const bobBal = await this.sushi.balanceOf(bob); 28 | const carolBal = await this.sushi.balanceOf(carol); 29 | assert.equal(totalSupply.valueOf(), '1100'); 30 | assert.equal(aliceBal.valueOf(), '100'); 31 | assert.equal(bobBal.valueOf(), '1000'); 32 | assert.equal(carolBal.valueOf(), '0'); 33 | }); 34 | 35 | it('should supply token transfers properly', async () => { 36 | await this.sushi.mint(alice, '100', { from: alice }); 37 | await this.sushi.mint(bob, '1000', { from: alice }); 38 | await this.sushi.transfer(carol, '10', { from: alice }); 39 | await this.sushi.transfer(carol, '100', { from: bob }); 40 | const totalSupply = await this.sushi.totalSupply(); 41 | const aliceBal = await this.sushi.balanceOf(alice); 42 | const bobBal = await this.sushi.balanceOf(bob); 43 | const carolBal = await this.sushi.balanceOf(carol); 44 | assert.equal(totalSupply.valueOf(), '1100'); 45 | assert.equal(aliceBal.valueOf(), '90'); 46 | assert.equal(bobBal.valueOf(), '900'); 47 | assert.equal(carolBal.valueOf(), '110'); 48 | }); 49 | 50 | it('should fail if you try to do bad transfers', async () => { 51 | await this.sushi.mint(alice, '100', { from: alice }); 52 | await expectRevert( 53 | this.sushi.transfer(carol, '110', { from: alice }), 54 | 'ERC20: transfer amount exceeds balance', 55 | ); 56 | await expectRevert( 57 | this.sushi.transfer(carol, '1', { from: bob }), 58 | 'ERC20: transfer amount exceeds balance', 59 | ); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/Timelock.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const ethers = require('ethers'); 3 | const SushiToken = artifacts.require('SushiToken'); 4 | const MasterChef = artifacts.require('MasterChef'); 5 | const MockERC20 = artifacts.require('MockERC20'); 6 | const Timelock = artifacts.require('Timelock'); 7 | 8 | function encodeParameters(types, values) { 9 | const abi = new ethers.utils.AbiCoder(); 10 | return abi.encode(types, values); 11 | } 12 | 13 | contract('Timelock', ([alice, bob, carol, dev, minter]) => { 14 | beforeEach(async () => { 15 | this.sushi = await SushiToken.new({ from: alice }); 16 | this.timelock = await Timelock.new(bob, '259200', { from: alice }); 17 | }); 18 | 19 | it('should not allow non-owner to do operation', async () => { 20 | await this.sushi.transferOwnership(this.timelock.address, { from: alice }); 21 | await expectRevert( 22 | this.sushi.transferOwnership(carol, { from: alice }), 23 | 'Ownable: caller is not the owner', 24 | ); 25 | await expectRevert( 26 | this.sushi.transferOwnership(carol, { from: bob }), 27 | 'Ownable: caller is not the owner', 28 | ); 29 | await expectRevert( 30 | this.timelock.queueTransaction( 31 | this.sushi.address, '0', 'transferOwnership(address)', 32 | encodeParameters(['address'], [carol]), 33 | (await time.latest()).add(time.duration.days(4)), 34 | { from: alice }, 35 | ), 36 | 'Timelock::queueTransaction: Call must come from admin.', 37 | ); 38 | }); 39 | 40 | it('should do the timelock thing', async () => { 41 | await this.sushi.transferOwnership(this.timelock.address, { from: alice }); 42 | const eta = (await time.latest()).add(time.duration.days(4)); 43 | await this.timelock.queueTransaction( 44 | this.sushi.address, '0', 'transferOwnership(address)', 45 | encodeParameters(['address'], [carol]), eta, { from: bob }, 46 | ); 47 | await time.increase(time.duration.days(1)); 48 | await expectRevert( 49 | this.timelock.executeTransaction( 50 | this.sushi.address, '0', 'transferOwnership(address)', 51 | encodeParameters(['address'], [carol]), eta, { from: bob }, 52 | ), 53 | "Timelock::executeTransaction: Transaction hasn't surpassed time lock.", 54 | ); 55 | await time.increase(time.duration.days(4)); 56 | await this.timelock.executeTransaction( 57 | this.sushi.address, '0', 'transferOwnership(address)', 58 | encodeParameters(['address'], [carol]), eta, { from: bob }, 59 | ); 60 | assert.equal((await this.sushi.owner()).valueOf(), carol); 61 | }); 62 | 63 | it('should also work with MasterChef', async () => { 64 | this.lp1 = await MockERC20.new('LPToken', 'LP', '10000000000', { from: minter }); 65 | this.lp2 = await MockERC20.new('LPToken', 'LP', '10000000000', { from: minter }); 66 | this.chef = await MasterChef.new(this.sushi.address, dev, '1000', '0', '1000', { from: alice }); 67 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 68 | await this.chef.add('100', this.lp1.address, true); 69 | await this.chef.transferOwnership(this.timelock.address, { from: alice }); 70 | const eta = (await time.latest()).add(time.duration.days(4)); 71 | await this.timelock.queueTransaction( 72 | this.chef.address, '0', 'set(uint256,uint256,bool)', 73 | encodeParameters(['uint256', 'uint256', 'bool'], ['0', '200', false]), eta, { from: bob }, 74 | ); 75 | await this.timelock.queueTransaction( 76 | this.chef.address, '0', 'add(uint256,address,bool)', 77 | encodeParameters(['uint256', 'address', 'bool'], ['100', this.lp2.address, false]), eta, { from: bob }, 78 | ); 79 | await time.increase(time.duration.days(4)); 80 | await this.timelock.executeTransaction( 81 | this.chef.address, '0', 'set(uint256,uint256,bool)', 82 | encodeParameters(['uint256', 'uint256', 'bool'], ['0', '200', false]), eta, { from: bob }, 83 | ); 84 | await this.timelock.executeTransaction( 85 | this.chef.address, '0', 'add(uint256,address,bool)', 86 | encodeParameters(['uint256', 'address', 'bool'], ['100', this.lp2.address, false]), eta, { from: bob }, 87 | ); 88 | assert.equal((await this.chef.poolInfo('0')).valueOf().allocPoint, '200'); 89 | assert.equal((await this.chef.totalAllocPoint()).valueOf(), '300'); 90 | assert.equal((await this.chef.poolLength()).valueOf(), '2'); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/ZGovernor.test.js: -------------------------------------------------------------------------------- 1 | const { expectRevert, time } = require('@openzeppelin/test-helpers'); 2 | const ethers = require('ethers'); 3 | const SushiToken = artifacts.require('SushiToken'); 4 | const MasterChef = artifacts.require('MasterChef'); 5 | const Timelock = artifacts.require('Timelock'); 6 | const GovernorAlpha = artifacts.require('GovernorAlpha'); 7 | const MockERC20 = artifacts.require('MockERC20'); 8 | 9 | function encodeParameters(types, values) { 10 | const abi = new ethers.utils.AbiCoder(); 11 | return abi.encode(types, values); 12 | } 13 | 14 | contract('Governor', ([alice, minter, dev]) => { 15 | it('should work', async () => { 16 | this.sushi = await SushiToken.new({ from: alice }); 17 | await this.sushi.delegate(dev, { from: dev }); 18 | this.chef = await MasterChef.new(this.sushi.address, dev, '100', '0', '0', { from: alice }); 19 | await this.sushi.transferOwnership(this.chef.address, { from: alice }); 20 | this.lp = await MockERC20.new('LPToken', 'LP', '10000000000', { from: minter }); 21 | this.lp2 = await MockERC20.new('LPToken2', 'LP2', '10000000000', { from: minter }); 22 | await this.chef.add('100', this.lp.address, true, { from: alice }); 23 | await this.lp.approve(this.chef.address, '1000', { from: minter }); 24 | await this.chef.deposit(0, '100', { from: minter }); 25 | // Perform another deposit to make sure some SUSHIs are minted in that 1 block. 26 | await this.chef.deposit(0, '100', { from: minter }); 27 | assert.equal((await this.sushi.totalSupply()).valueOf(), '110'); 28 | assert.equal((await this.sushi.balanceOf(minter)).valueOf(), '100'); 29 | assert.equal((await this.sushi.balanceOf(dev)).valueOf(), '10'); 30 | // Transfer ownership to timelock contract 31 | this.timelock = await Timelock.new(alice, time.duration.days(2), { from: alice }); 32 | this.gov = await GovernorAlpha.new(this.timelock.address, this.sushi.address, alice, { from: alice }); 33 | await this.timelock.setPendingAdmin(this.gov.address, { from: alice }); 34 | await this.gov.__acceptAdmin({ from: alice }); 35 | await this.chef.transferOwnership(this.timelock.address, { from: alice }); 36 | await expectRevert( 37 | this.chef.add('100', this.lp2.address, true, { from: alice }), 38 | 'Ownable: caller is not the owner', 39 | ); 40 | await expectRevert( 41 | this.gov.propose( 42 | [this.chef.address], ['0'], ['add(uint256,address,bool)'], 43 | [encodeParameters(['uint256', 'address', 'bool'], ['100', this.lp2.address, true])], 44 | 'Add LP2', 45 | { from: alice }, 46 | ), 47 | 'GovernorAlpha::propose: proposer votes below proposal threshold', 48 | ); 49 | await this.gov.propose( 50 | [this.chef.address], ['0'], ['add(uint256,address,bool)'], 51 | [encodeParameters(['uint256', 'address', 'bool'], ['100', this.lp2.address, true])], 52 | 'Add LP2', 53 | { from: dev }, 54 | ); 55 | await time.advanceBlock(); 56 | await this.gov.castVote('1', true, { from: dev }); 57 | await expectRevert(this.gov.queue('1'), "GovernorAlpha::queue: proposal can only be queued if it is succeeded"); 58 | console.log("Advancing 17280 blocks. Will take a while..."); 59 | for (let i = 0; i < 17280; ++i) { 60 | await time.advanceBlock(); 61 | } 62 | await this.gov.queue('1'); 63 | await expectRevert(this.gov.execute('1'), "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); 64 | await time.increase(time.duration.days(3)); 65 | await this.gov.execute('1'); 66 | assert.equal((await this.chef.poolLength()).valueOf(), '2'); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | require('dotenv').config(); 3 | 4 | const HDWalletProvider = require('@truffle/hdwallet-provider'); 5 | const infuraKey = process.env.infuraKey; 6 | const mnemonic = process.env.mnemonic; 7 | module.exports = { 8 | 9 | networks: { 10 | // Useful for testing. The `development` name is special - truffle uses it by default 11 | // if it's defined here and no other network is specified at the command line. 12 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 13 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 14 | // options below to some value. 15 | // 16 | 17 | ganache: { 18 | host: "127.0.0.1", // Localhost (default: none) 19 | port: 7545, // Standard Ethereum port (default: none) 20 | network_id: "*", // Any network (default: none) 21 | }, 22 | development: { 23 | host: "127.0.0.1", // Localhost (default: none) 24 | port: 8545, // Standard Ethereum port (default: none) 25 | network_id: "*", // Any network (default: none) 26 | }, 27 | develop: { 28 | host: "127.0.0.1", // Localhost (default: none) 29 | port: 8545, // Standard Ethereum port (default: none) 30 | network_id: "*", // Any network (default: none) 31 | gas: 8500000, // Gas sent with each transaction (default: ~6700000) 32 | gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 33 | }, 34 | 35 | // Another network with more advanced options... 36 | // advanced: { 37 | // port: 8777, // Custom port 38 | // network_id: 1342, // Custom network 39 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 40 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 41 | // from:
, // Account to send txs from (default: accounts[0]) 42 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 43 | // }, 44 | 45 | // Useful for deploying to a public network. 46 | // NB: It's important to wrap the provider as a function. 47 | ropsten: { 48 | provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/` + infuraKey), 49 | network_id: 3, // Ropsten's id 50 | gas: 5500000, // Ropsten has a lower block limit than mainnet 51 | confirmations: 2, // # of confs to wait between deployments. (default: 0) 52 | timeoutBlocks: 20000, // # of blocks before a deployment times out (minimum/default: 50) 53 | skipDryRun: true, // Skip dry run before migrations? (default: false for public nets ) 54 | port: 8545 55 | }, 56 | 57 | rinkeby: { 58 | provider: () => new HDWalletProvider(mnemonic, `https://rinkeby.infura.io/v3/` + infuraKey), 59 | network_id: 4, 60 | gas: 6500000, 61 | skipDryRun: true 62 | }, 63 | kovan: { 64 | provider: () => new HDWalletProvider(mnemonic, `https://kovan.infura.io/v3/` + infuraKey), 65 | network_id: 42, 66 | gas: 5500000, 67 | skipDryRun: true 68 | }, 69 | mainnet: { 70 | provider: () => new HDWalletProvider(mnemonic, `https://mainnet.infura.io/v3/` + infuraKey), 71 | network_id: 1, 72 | gas: 5500000, 73 | skipDryRun: true 74 | }, 75 | // Useful for private networks 76 | // private: { 77 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 78 | // network_id: 2111, // This network is yours, in the cloud. 79 | // production: true // Treats this network as if it was a public net. (default: false) 80 | // } 81 | }, 82 | compilers: { 83 | solc: { 84 | version: "0.6.12", 85 | settings: { // See the solidity docs for advice about optimization and evmVersion 86 | optimizer: { 87 | enabled: true, 88 | runs: 200 89 | }, 90 | // evmVersion: "istanbul" 91 | } 92 | } 93 | }, 94 | plugins: [ 95 | 'truffle-plugin-verify' 96 | ], 97 | api_keys: { 98 | etherscan: process.env.ETHERSCAN_API_KEY 99 | } 100 | }; --------------------------------------------------------------------------------