tp钱包下载官网|uniswap合约地址
tp钱包下载官网|uniswap合约地址
手把手教你部署自己的uniswap交易所 - 知乎
手把手教你部署自己的uniswap交易所 - 知乎首发于登链社区切换模式写文章登录/注册手把手教你部署自己的uniswap交易所登链社区区块链技术爱好者的家园本文作者:崔棉大师演示地址: https://fankouzu.github.io/cuiswap/ (请在测试网使用)准备Uniswap合约源码源码结构Uniswap在Github上面开源了全部合约代码,其中包括核心合约,周边合约两部分.Uniswap还开源了前端代码,前端代码使用React开发核心合约周边合约前端代码在Uniswap的核心代码中,主要包含3个合约:工厂合约,配对合约,ERC20合约.其中配对合约继承了ERC20合约,我们可以把它们看作一个合约.工厂合约通过create2方法部署配对合约,所以在部署合约时只需要部署工厂合约.周边合约中包括一些示例代码,例如价格预言机,闪电交换,其中最重要的是路由合约.在周边合约的代码库中,包含两个路由合约:UnsiwapV2Router01,UnsiwapV2Router02.工厂合约和配对合约需要通过路由合约调用才能更好的完成交易所的全部功能,所以我们还要部署路由合约两个合约大部分相同,有小部分不同,如果将两个合约的差异化合并成一个合约,部署的时候将会出现out of gas,所以才被分成了两个合约.常用功能两个合约中都包括,所以我们部署其中任意一个路由合约都可以继承引用调用ERC20合约配对合约工厂合约路由合约从浏览器中下载合约源码如果你对合约代码并不熟悉,也可以跳过上面这部分,接下来我们将从以太坊浏览器中直接拷贝线上版合约源码工厂合约路由合约01[可选]路由合约02部署合约准备部署账户Uniswap的路由合约部署在以太坊的主网和Ropsten,Rinkeby,Goerli,Kovan几个测试网的合约地址都是相同的,这样可以使Uniswap的前端不管切换到任何一个网络,路由地址都不会变.要想实现这个相同地址的部署,我们需要准备一个全新的账户用来部署合约.全新的账户指的是在部署合约之前的nonce值为0.因为合约的地址是根据你的账户地址和nonce值计算出来的,所以在不同网络中,如果nonce值相同,部署出的合约地址也相同.通过助记词生成新账户可以通过我之前录制的视频学习操作方法B站视频油管视频生成好助记词之后,记得用英文助记词,保存好助记词,还有助记词对应的账户地址向新地址转帐ETH部署合约需要的gas费约为0.18个Ether,目前主网可能需要的更多.通过一个已有Ether的账户向新账户转帐.测试网的Ether可以通过每个测试网的水龙头申请到测试币.获取测试币方法转账完成后,将助记词导入到Metamask中准备WETH合约地址在部署路由合约时,构造函数中需要填入工厂合约的地址和WETH合约的地址,由于WETH合约的地址在主网和测试网的地址都不相同,所以需要找到每个网络中WETH合约的地址.WETH合约用于将Eth交换为erc20的Eth,由于Eth不是erc20的token,所以我们必须使用WETH作为交换媒介{
mainnet:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
ropsten:'0xc778417E063141139Fce010982780140Aa0cD5Ab',
rinkeby:'0xc778417E063141139Fce010982780140Aa0cD5Ab',
goerli:'0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
kovan:'0xd0A1E359811322d97991E03f863a0C30C2cF029C'
}申请infuraKey在部署合约之前,我们还需要使用infura作为免费节点,所以需要申请一个infuraKey申请地址:infura.io申请方法使用remix部署合约将工厂合约和路由合约的线上版本导入到remix中,在编译合约的选项中,EVM VERSION选择istanbul,COMPILER CONFIGURATION中选择Enable optimization部署顺序和构造函数部署工厂合约构造函数的参数是一个自己常用的账户地址部署路由合约01 [可选], 02构造函数的参数1是工厂合约的地址参数2 是当前网络中WETH合约的地址,参考前文部署Uniswap前端克隆前端代码在项目目录运行命令:$ git clone https://github.com/Uniswap/uniswap-interface.git安装依赖库在项目目录运行命令:$ cd uniswap-interface
$ yarn安装完成后,可以先测试运行一下,在uniswap-interface目录运行命令$ yarn start如果运行成功,将会打开一个浏览器,同时打开Uniswap的前端界面修改路由地址在Uniswap的前端中以常量的形式定义了Uniswap的路由地址,我们只需要修改路由地址就可以让前端链接到你的路由合约中修改文件: 项目目录/uniswap-interface/src/constants/index.ts 第6行import { AbstractConnector } from '@web3-react/abstract-connector'
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' //修改成你的路由合约地址
......保存后运行yarn start即可看到效果将代码部署到GitHub Pages创建GitHub项目创建项目的方法就不在这里讲了,不会的同学可以去搜索一下将前端代码添加到GitHub项目仓库首先要删除原先Uniswap项目中的.git目录,在项目目录运行命令:$ cd uniswap-interface
$ rm -rf .git然后初始化git,并将Unsiwap前端代码添加到自己的项目仓库中git init
git remote add origin https://github.com/用户名/项目名.git安装并部署gh-pages我们将通过gh-pages模块将前端代码部署到github.io,在前端代码的目录运行:$ yarn add gh-pages接下来要编译react和部署gh-pages,在前端代码的目录运行:$ yarn build修改前端代码目录中的package.json$ vim package.json
{
"name": "@uniswap/interface",
"description": "Uniswap Interface",
"homepage": "https://用户名.github.io/项目名称",//修改这里
......
// 添加部署的脚本,还是在package.json中
......
"scripts": {
......
"deploy": "gh-pages -d build" //添加这一行
},保存退出之后,在前端代码的目录运行:$ git add .
$ git commit -m "first commit"
$ git push
$ yarn deploy现在在浏览器中打开https://用户名.github.io/项目名称/index.html就可以打开自己的交易所啦.如果不输入地址结尾的index.html在项目刚部署之后会报错,过一段时间就可以不输入了.扩展部署自己的weth可以将以太坊浏览器中的weth源码拷贝下来,自己部署一个属于自己的weth合约可信token列表Uniswap有一个自己的可信token列表,同样被设置在项目目录/uniswap-interface/src/constants/index.ts文件中,在最后一行就是.你可以将这个链接地址的文件拷贝下来,设置成自己需要的可信token地址列表,然后上传到github目录中,再修改index.ts文件中的链接地址,这样就可以让你自己的交易所中拥有自己设置的可信token列表了原文链接:https://learnblockchain.cn...作者主页:https://learnblockchain.cn... ,欢迎阅读作者更多好文登链社区-区块链技术爱好者的家园发布于 2020-09-02 15:19交易所区块链技术赞同 162 条评论分享喜欢收藏申请转载文章被以下专栏收录登链社区高质量区块链技术文
Uniswap-v2 合约概览 | ethereum.org
wap-v2 合约概览 | ethereum.org跳转至主要内容学习用法构建参与研究搜索语言 ZH帮助更新此页面本页面有新版本,但现在只有英文版。请帮助我们翻译最新版本。翻译页面没有错误!此页面未翻译,因此特意以英文显示。不再显示Uniswap-v2 合约概览solidity中级Ori Pomerantz 2021年5月1日81 分钟阅读 minute read在本页面介绍Uniswap 是做什么的?为什么选择 v2? 而不是 v3?核心合约与外围合约数据和控制流程兑换增加流动资金撤回流动资金核心合约UniswapV2Pair.solUniswapV2Factory.solUniswapV2ERC20.sol外围合约UniswapV2Router01.solUniswapV2Router02.solUniswapV2Migrator.sol程序库数学定点小数 (UQ112x112)UniswapV2Library转账帮助结论介绍Uniswap v2(opens in a new tab) 可以在任何两个 ERC-20 代币之间创建一个兑换市场。 在本文中,我们将深入探讨实现此协议的合约的源代码,了解为何要如此编写协议。Uniswap 是做什么的?一般来说有两类用户:流动资金提供者和交易者。流动性提供者为资金池提供两种可以兑换的代币(称为 Token0 和 Token1)。 作为回报,他们会收到第三种叫做流动性代币的代币,代表他们对资金池的部分所有权。交易者将一种代币发送到资金池,并从资金池中接收流动性提供者提供的另一种代币(例如,发送 Token0 并获得 Token1)。 兑换汇率由资金池中 Token0 和 Token1 的相对数量决定。 此外,资金池将收取汇率的一小部分作为流动性资金池的奖励。当流动性提供者想要收回他们的代币资产时,他们可以销毁资金池代币并收回他们的代币,其中包括属于他们的奖励。点击此处查看更完整的描述(opens in a new tab)。为什么选择 v2? 而不是 v3?Uniswap v3(opens in a new tab) 是 v2 的升级,远比 v2 复杂得多。 比较容易的方法是先学习 v2,然后再学习 v3。核心合约与外围合约Uniswap v2 可以分为两个部分,一个为核心部分,另一个为外围部分。 核心合约存放着资产,因而必须确保安全,这种分法就使核心合约更加简洁且更便于审核。 而所有交易者需要的其它功能可以通过外围合约提供。数据和控制流程执行 Uniswap 的三个主要操作时,会出现以下数据和控制流程:兑换不同代币将资金添加到市场中提供流动性,并获得兑换中奖励的流动池 ERC-20 代币消耗流动池 ERC-20 代币并收回交易所允许交易者兑换的 ERC-20 代币兑换这是交易者最常用的流程:调用者向外围帐户提供兑换额度。调用外围合约中的一个兑换函数。外围合约有多种兑换函数,调用哪一个取决于是否涉及以太币、交易者是指定了存入的代币金额还是提取的代币金额等。 每个兑换函数都接受一个 path,即要执行的一系列兑换。在外围合约 (UniswapV2Router02.sol) 中确定兑换路径中,每次兑换所需交易的代币数额。沿路径迭代。 对于路径上的每次兑换,首先发送输入代币,然后调用交易所的 swap 函数。 在大多数情况下,代币输出的目的地址是路径中下一个配对交易。 在最后一个交易所中,该地址是交易者提供的地址。在核心合约 (UniswapV2Pair.sol) 中验证核心合约没有被欺骗,可在兑换后保持足够的流动资金。检查除了现有的储备金额外,还有多少额外的代币。 此数额是我们收到的要用于兑换的输入代币数量。将输出代币发送到目的地址。调用 _update 来更新储备金额回到外围合约 (UniswapV2Router02.sol)执行所需的必要清理工作(例如,消耗包装以太币代币以返回以太币给交易者)增加流动资金调用者向外围帐户提交准备加入流动资金池的资金额度。调用外围合约的其中一个 addLiquidity 函数。在外围合约 (UniswapV2Router02.sol) 中必要时创建一个新的配对交易如果有现有的币对交易所,请计算要增加的代币金额。 该金额对于两种代币应该是相同的,因此新代币对现有代币的比率是相同的。检查金额是否可接受(调用者可以指定一个最低金额,低于此金额他们就不增加流动性)调用核心合约。在核心合约 (UniswapV2Pair.sol) 中生成流动池代币并将其发送给调用者调用 _update 来更新储备金额撤回流动资金调用者向外围帐户提供一个流动池代币的额度,作为兑换底层代币所需的消耗。调用外围合约的其中一个 removeLiquidity 函数。在外围合约 (UniswapV2Router02.sol) 中将流动池代币发送到该配对交易在核心合约 (UniswapV2Pair.sol) 中向目的地址发送底层代币,金额与销毁的代币成比例。 例如,如果资金池里有 1000 个 A 代币,500 个 B 代币和 90 个流动性代币,而我们收到请求销毁 9 个流动性代币,那么,我们将销毁 10% 的流动性代币,然后将返还用户 100 个 A 代币和 50 个 B 代币。销毁流动性代币调用_update来更新储备金额核心合约这些是持有流动资金的安全合约。UniswapV2Pair.sol本合约(opens in a new tab)实现用于交易代币的实际资金池。 这是 Uniswap 的核心功能。1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Pair.sol';4import './UniswapV2ERC20.sol';5import './libraries/Math.sol';6import './libraries/UQ112x112.sol';7import './interfaces/IERC20.sol';8import './interfaces/IUniswapV2Factory.sol';9import './interfaces/IUniswapV2Callee.sol';显示全部 复制这些都是该合约需要知道的接口,因为该合约实现了它们(IUniswapV2Pair 和 UniswapV2ERC20),或因为该合约调用了实现它们的合约。1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { 复制此合约继承自 UniswapV2ERC20,为流动池代币提供 ERC-20 代币功能。1 using SafeMath for uint; 复制SafeMath 库(opens in a new tab)用于避免整数上溢和下溢。 这很重要,否则最终可能会出现这样的情况:本该是 -1 的值,结果却成了 2^256-1。1 using UQ112x112 for uint224; 复制流动池合约中的许多计算都需要分数。 但是,以太坊虚拟机本身不支持分数。 Uniswap 找到的解决方案是使用 224 位数值,整数部分为 112 位,小数部分为 112 位。 因此,1.0 用 2^112 表示,1.5 用 2^112 + 2^111 表示,以此类推。关于这个函数库的更详细内容在文档的稍后部分。变量1 uint public constant MINIMUM_LIQUIDITY = 10**3; 复制为了避免分母为零的情况,始终存在最低数量的流动性代币(但为帐户零所拥有)。 该数字,即 MINIMUM_LIQUIDITY,为 1000。1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)'))); 复制这是 ERC-20 传输函数的应用程序二进制接口选择程序。 它用于在两个代币帐户中转移 ERC-20 代币。1 address public factory; 复制这就是由工厂合约创造的资金池地址。 每个资金池都是两种 ERC-20 代币之间的交易所,工厂是连接所有这些资金池的中心点。1 address public token0;2 address public token1; 复制这两个地址是该资金池可以交易的两种 ERC-20 代币的合约地址。1 uint112 private reserve0; // uses single storage slot, accessible via getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReserves 复制每个代币类型都有储备的资源库。 我们假定两者代表相同数量的值,因此每个 token0 的价值都等同于 reserve1/reserve0 token1。1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves 复制发生兑换的最后一个区块的时间戳,用来追踪一段时间内的汇率。以太坊合约中燃料消耗量最大的一项是存储,这种燃料消耗从一次合约调用持续到下一次调用。 每个存储单元长度为 256 位。 因此,reserve0、reserve1 和 blockTimestampLast 三个变量的分配方式让单个存储值可以包含全部这三个变量 (112+112+32=256)。1 uint public price0CumulativeLast;2 uint public price1CumulativeLast; 复制这些变量存放每种代币的累计成本(每种代币在另一种代币的基础上计算)。 可以用来计算一段时间内的平均汇率。1 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event 复制币对交易所决定 token0 和 token1 之间汇率的方式是在交易中保持两种储备金的乘数恒定不变。 即 kLast 这个值。 当流动性提供者存入或提取代币时,该乘数就会变化,由于兑换市场的费用为 0.3%,它会略有增加。下面是一个示例。 请注意,为了简单起见,表格中的数字仅保留了小数点后三位,我们忽略了 0.3% 交易费,因此数字并不准确。事件reserve0reserve1reserve0 * reserve1平均汇率 (token1 / token0)初始设置1,000.0001,000.0001,000,000交易者 A 用 50 个 token0 兑换 47.619 个 token11,050.000952.3811,000,0000.952交易者 B 用 10 个 token0 兑换 8.984 个 token11,060.000943.3961,000,0000.898交易者 C 用 40 个 token0 兑换 34.305 个 token11,100.000909.0901,000,0000.858交易者 D 用 100 个 token1 兑换 109.01 个 token0990.9901,009.0901,000,0000.917交易者 E 用 10 个 token0 兑换 10.079 个 token11,000.990999.0101,000,0001.008由于交易者提供了更多 token0,token1 的相对价值增加了,反之亦然,这取决于供求。锁定1 uint private unlocked = 1; 复制有一类基于重入攻击(opens in a new tab)的安全漏洞。 Uniswap 需要转让不同数值的 ERC-20 代币,这意味着调用的 ERC-20 合约可能会导致调用合约的 Uniswap 市场遭受攻击。 通过在合约中使用 unlocked 变量,我们可以防止函数在运行时被调用(同一笔交易中)。1 modifier lock() { 复制此函数是一个修改器(opens in a new tab),它对正常函数进行包装数,以便以某种方式改变其行为。1 require(unlocked == 1, 'UniswapV2: LOCKED');2 unlocked = 0; 复制如果 unlocked 变量值为 1,将其设置为 0。 如果已经是 0,则撤销调用,返回失败。1 _; 复制在修饰符中,_; 是原始函数调用(含所有参数)。 此处,这意味着仅在 unlocked 变量值为 1 时调用函数,该函数调用才有效;而当函数运行时,unlocked 值为 0。1 unlocked = 1;2 } 复制当主函数返回后,释放锁定。其他 函数1 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {2 _reserve0 = reserve0;3 _reserve1 = reserve1;4 _blockTimestampLast = blockTimestampLast;5 } 复制此函数返回给调用者当前的兑换状态。 请注意,Solidity 函数可以返回多个值(opens in a new tab)。1 function _safeTransfer(address token, address to, uint value) private {2 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); 复制此内部函数可以从交易所转账一定数额的 ERC20 代币给其他帐户。 SELECTOR 指定我们调用的函数是 transfer(address,uint)(参见上面的定义)。为了避免必须为代币函数导入接口,我们需要使用其中一个应用程序二进制接口函数(opens in a new tab)来“手动”创建调用。1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');2 } 复制ERC-20 的转移调用有两种方式可能失败:回滚 如果对外部合约的调用回滚,则布尔返回值为 false正常结束但报告失败。 在这种情况下,返回值的缓冲为非零长度,将其解码为布尔值时,其值为 false一旦出现这两种情况,转移调用就会回退。事件1 event Mint(address indexed sender, uint amount0, uint amount1);2 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 复制当流动资金提供者存入流动资金 (Mint) 或提取流动资金 (Burn) 时,会发出这两个事件。 在这两种情况下,存入或提取的 token0 和 token1 金额是事件的一部分,以及调用合约的帐户身份 (Sender) 也是事件的一部分。 在提取资金时,事件中还包括获得代币的目的地址 (to),这个地址可能与发送人不同。1 event Swap(2 address indexed sender,3 uint amount0In,4 uint amount1In,5 uint amount0Out,6 uint amount1Out,7 address indexed to8 ); 复制当交易者用一种代币交换另一种代币时,会激发此事件。 同样,代币发送者和兑换后代币的存入目的帐户可能不一样。 每种代币都可以发送到交易所,或者从交易所接收。1 event Sync(uint112 reserve0, uint112 reserve1); 复制最后,无论出于何种原因,每次存入或提取代币时都会触发 Sync 事件,以提供最新的储备金信息(从而提供汇率)。设置函数这些函数应在建立新的配对交易时调用。1 constructor() public {2 factory = msg.sender;3 } 复制构造函数确保我们能够跟踪产生配对的工厂合约的地址。 initialize 函数和工厂交易费(如果有)需要此信息1 // called once by the factory at time of deployment2 function initialize(address _token0, address _token1) external {3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check4 token0 = _token0;5 token1 = _token1;6 } 复制这个函数允许工厂(而且只允许工厂)指定配对中进行兑换的两种 ERC-20 代币。内部更新函数_update1 // update reserves and, on the first call per block, price accumulators2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { 复制每次存入或提取代币时,会调用此函数。1 require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); 复制如果 balance0 或 balance1 (uint256) 大于 uint112(-1) (=2^112-1)(因此当转换为 uint112 时会溢出并返回 0),拒绝继续执行 _update 以防止溢出。 一般的代币可以细分成 10^18 个单元,这意味在每个交易所,每种代币的限额为 5.1*10^15 个。 迄今为止,这并不是一个问题。1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { 复制如果流逝的时间值不是零,这意味着本交易是此区块上的第一笔兑换交易。 在这种情况下,我们需要更新累积成本值。1 // * never overflows, and + overflow is desired2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;4 } 复制每个累积成本值都用最新成本值(另一个代币的储备金额/本代币的储备金额)与以秒为单位的流逝时间的乘积加以更新。 要获得平均兑换价格,需要读取两个时间点的累积价格并除以两个时间点之间的时间差。 例如,假设下面这些事件序列:事件reserve0reserve1时间戳边际汇率 (reserve1 / reserve0)price0CumulativeLast初始设置1,000.0001,000.0005,0001.0000交易者 A 存入 50 个代币 0 获得 47.619 个代币 11,050.000952.3815,0200.90720交易者 B 存入 10 个代币 0 获得 8.984 个代币 11,060.000943.3965,0300.8920+10*0.907 = 29.07交易者 C 存入 40 个代币 0 获得 34.305 个代币 11,100.000909.0905,1000.82629.07+70*0.890 = 91.37交易者 D 存入 100 个代币 0 获得 109.01 个代币 1990.9901,009.0905,1101.01891.37+10*0.826 = 99.63交易者 E 存入 10 个代币 0 获得 10.079 个代币 11,000.990999.0105,1500.99899.63+40*1.1018 = 143.702比如说我们想要计算时间戳 5,030 到 5,150 之间代币 0 的平均价格。 price0Cumulative 的差值为 143.702-29.07=114.632。 此为两分钟(120 秒)间的平均值。 因此,平均价格为 114.632/120 = 0.955。此价格计算是我们需要知道原有资金储备规模的原因。1 reserve0 = uint112(balance0);2 reserve1 = uint112(balance1);3 blockTimestampLast = blockTimestamp;4 emit Sync(reserve0, reserve1);5 } 复制最后,更新全局变量并发布一个 Sync 事件。_mintFee1 // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) { 复制在 Uniswap 2.0 的合约中规定交易者为使用兑换市场支付 0.30% 的费用。 这笔费用的大部分(交易的 0.25%)支付给流动性提供者。 余下的 0.05% 可以支付给流动性提供者或支付给工厂指定的地址作为协议费,用于支付 Uniswap 团队的开发费用。为了减少计算次数(因此减少燃料费用),仅在向资金池中增加或减少流动性时才计算该费用,而不是在每次兑换交易时都计算。1 address feeTo = IUniswapV2Factory(factory).feeTo();2 feeOn = feeTo != address(0); 复制读取工厂的费用支付地址。 如果返回值为零,则代表没有协议费,也不需要计算这笔费用。1 uint _kLast = kLast; // gas savings 复制kLast 状态变量位于内存中,所以在合约的不同调用中都有一个值。 虽然易失性内存每次在函数调用合约结束后都会清空,但由于访问存储的费用比访问内存高得多,所以我们使用内部变量,以降低燃料费用。1 if (feeOn) {2 if (_kLast != 0) { 复制流动资金提供者仅仅因为提供流动性代币而得到所属的费用。 但是协议费用要求铸造新的流动性代币,并提供给 feeTo 地址。1 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));2 uint rootKLast = Math.sqrt(_kLast);3 if (rootK > rootKLast) { 复制如果有新的流动性变化需要收取协议费。 你可以在本文后面部分看到平方根函数。1 uint numerator = totalSupply.mul(rootK.sub(rootKLast));2 uint denominator = rootK.mul(5).add(rootKLast);3 uint liquidity = numerator / denominator; 复制这种复杂的费用计算方法在白皮书(opens in a new tab)第 5 页中作了解释。 从计算 kLast 的时间到当前为止,流动性没有增加或减少(因为每次计算都是在流动性增加或减少并发生实际变化之前进行),所以 reserve0 * reserve1 的任何变化一定是从交易费用中产生(如果没有交易费,reserve0 * reserve1 值为常量)。1 if (liquidity > 0) _mint(feeTo, liquidity);2 }3 } 复制使用 UniswapV2ERC20._mint 函数产生更多的流动池代币并发送到 feeTo 地址。1 } else if (_kLast != 0) {2 kLast = 0;3 }4 } 复制如果不需收费则将 klast 设为 0(如果 klast 不为 0)。 编写该合约时,有一个燃料返还功能(opens in a new tab),用于鼓励合约将其不需要的存储释放,从而减少以太坊上状态的整体存储大小。 此段代码在可行时返还。外部可访问函数请注意,虽然任何交易或合约都可以调用这些函数,但这些函数在设计上是从外围合约调用。 如果直接调用,您无法欺骗币对交易所,但可能因为错误而丢失价值。铸币1 // this low-level function should be called from a contract which performs important safety checks2 function mint(address to) external lock returns (uint liquidity) { 复制当流动资金提供者为资金池增加流动资金时,将会调用此函数。 它铸造额外的流动性代币作为奖励。 应从外围合约中调用该函数,在同一笔交易中增加流动性后外围合约就调用该函数(因此其他人都不能在合法所有者之前提交要求新增加流动性的交易)。1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings 复制这是 Solidity 函数中读取多个返回值的方式。 我们丢弃了最后返回的值区块时间戳,因为不需要它。1 uint balance0 = IERC20(token0).balanceOf(address(this));2 uint balance1 = IERC20(token1).balanceOf(address(this));3 uint amount0 = balance0.sub(_reserve0);4 uint amount1 = balance1.sub(_reserve1); 复制获取当前余额并查看每个代币类型中添加的数量。1 bool feeOn = _mintFee(_reserve0, _reserve1); 复制如果有协议费用的话,计算需要收取的费用,并相应地产生流动池代币。 因为输入 _mintFee 函数的参数是原有的储备金数值,相应费用仅依据费用导致的资金池变化来精确计算。1 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee2 if (_totalSupply == 0) {3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);4 _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens 复制如果这是第一笔存款,会创建数量为 MINIMUM_LIQUIDITY 的代币并将它们发送到地址 0 进行锁定。 这些代币永远无法赎回,也就是说资金池永远不会完全变空(避免某些情况下出现分母为零错误)。 MINIMUM_LIQUIDITY 的值是 1000,因为考虑到大多数 ERC-20 细分成 1 个代币的 10^-18 个单位,而以太币则被分为 wei,为 1 个代币价值的 10^-15。 成本不高。在首次存入时,我们不知道两种代币的相对价值,所以假定两种代币都具有相同的价值,只需要两者数量的乘积并取一下平方根。我们可以相信这一点,因为提供同等价值、避免套利符合存款人的利益。 比方说,这两种代币的价值是相同的,但我们的存款人存入的 Token1 是 Token0 的四倍。 交易者可以利用币对交易所认为 Token0 的价值更高这种情况,减少其价值。事件reserve0reserve1reserve0 * reserve1流动池价值 (reserve0 + reserve1)初始设置83225640交易者存入 8 个 Token0 代币,获得 16 个 Token1 代币161625632正如您可以看到的,交易者额外获得了 8 个代币,这是由于流动池价值下降造成的,损害了拥有流动池的存款人。1 } else {2 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); 复制对于随后每次存入,我们已经知道两种资产之间的汇率。我们期望流动性提供者提供等值的两种代币。 如果他们没有,我们根据他们提供的较低价值代币来支付他们的流动池代币以做惩罚。无论是最初存入还是后续存入,流动性代币的数量均等于 reserve0*reserve1 变化的平方根,而流动性代币的价值不变(除非存入的资金为不等值的代币类型,那么就会分派“罚金”)。 下面是另一个示例,两种代币具有相同价值,进行了三次良性存入和一次不良存入(即只存入一种类型的代币,所以不会产生任何流动性代币)。事件reserve0reserve1reserve0 * reserve1流动池价值 (reserve0 + reserve1)存入资金而产生的流动池代币流动池代币总值每个流动池代币的值初始设置8.0008.0006416.000882.000每种代币存入 4 个12.00012.00014424.0004122.000每种代币存入 2 个14.00014.00019628.0002142.000不等值的存款18.00014.00025232.000014~2.286套利后~15.874~15.874252~31.748014~2.2671 }2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');3 _mint(to, liquidity); 复制使用 UniswapV2ERC20._mint 函数产生更多流动池代币并发送到正确的帐户地址。12 _update(balance0, balance1, _reserve0, _reserve1);3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date4 emit Mint(msg.sender, amount0, amount1);5 } 复制更新相应的状态变量(reserve0、reserve1,必要时还包含 kLast)并激发相应事件。销毁1 // this low-level function should be called from a contract which performs important safety checks2 function burn(address to) external lock returns (uint amount0, uint amount1) { 复制当流动资金被提取且相应的流动池代币需要被销毁时,将调用此函数。 还需要从外围帐户调用。1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings2 address _token0 = token0; // gas savings3 address _token1 = token1; // gas savings4 uint balance0 = IERC20(_token0).balanceOf(address(this));5 uint balance1 = IERC20(_token1).balanceOf(address(this));6 uint liquidity = balanceOf[address(this)]; 复制外围合约在调用函数之前,首先将要销毁的流动资金转到本合约中。 这样,我们知道有多少流动资金需要销毁,并可以确保它被销毁。1 bool feeOn = _mintFee(_reserve0, _reserve1);2 uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee3 amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution4 amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); 复制流动资金提供者获得等值数量的两种代币。 这样不会改变兑换汇率。1 _burn(address(this), liquidity);2 _safeTransfer(_token0, to, amount0);3 _safeTransfer(_token1, to, amount1);4 balance0 = IERC20(_token0).balanceOf(address(this));5 balance1 = IERC20(_token1).balanceOf(address(this));67 _update(balance0, balance1, _reserve0, _reserve1);8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date9 emit Burn(msg.sender, amount0, amount1, to);10 }11显示全部 复制burn 函数的其余部分是上述 mint 函数的镜像。兑换1 // this low-level function should be called from a contract which performs important safety checks2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { 复制此函数也应该从外围合约调用。1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');45 uint balance0;6 uint balance1;7 { // scope for _token{0,1}, avoids stack too deep errors 复制本地变量可以存储在内存中,或者如果变量数目不太多,直接存储进堆栈。 如果我们可以限制变量数量,那么建议使用堆栈以减少燃料消耗。 欲了解更多详情,请参阅以太坊黄皮书(以前的以太坊规范)(opens in a new tab)第 26 页上的“方程式 298”。1 address _token0 = token0;2 address _token1 = token1;3 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');4 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens 复制这种转移应该是会成功的,因为在转移之前我们确信所有条件都得到满足。 在以太坊中这样操作是可以的,原因在于如果在后面的调用中条件没有得到满足,我们可以回滚操作和造成的所有变化。1 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); 复制如果收到请求,则通知接收者要进行兑换。1 balance0 = IERC20(_token0).balanceOf(address(this));2 balance1 = IERC20(_token1).balanceOf(address(this));3 } 复制获取当前余额。 外围合约在调用交换函数之前,需要向合约发送要兑换的代币。 这让合约可以方便检查它有没有受到欺骗,这是在核心合约中必须进行的检查(因为除外围合约之外的其他实体也可以调用该函数)。1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');4 { // scope for reserve{0,1}Adjusted, avoids stack too deep errors5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); 复制这是一项健全性检查,确保我们不会因兑换而损失代币。 在任何情况下兑换都不应减少 reserve0*reserve1。 这也是我们确保为兑换发送 0.3% 费用的方式;在对 K 值进行完整性检查之前,我们将两个余额乘以 1000 减去 3 倍的金额,这意味着在将其 K 值与当前准备金 K 值进行比较之前,从余额中扣除 0.3% (3/1000 = 0.003 = 0.3%)。1 }23 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 } 复制更新 reserve0 和 reserve1 的值,并在必要时更新价格累积值和时间戳并激发相应事件。同步或提取实际余额有可能与配对交易所认为的储备金余额没有同步。 没有合约的认同,就无法撤回代币,但存款却不同。 帐户可以将代币转移到交易所,而无需调用 mint 或 swap。在这种情况下,有两种解决办法:sync,将储备金更新为当前余额skim,撤回额外的金额。 请注意任何帐户都可以调用 skim 函数,因为无法知道是谁存入的代币。 此信息是在一个事件中发布的,但这些事件无法从区块链中访问。1 // force balances to match reserves2 function skim(address to) external lock {3 address _token0 = token0; // gas savings4 address _token1 = token1; // gas savings5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));7 }891011 // force reserves to match balances12 function sync() external lock {13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);14 }15}显示全部 复制UniswapV2Factory.sol此合约(opens in a new tab)创建币对交易所。1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';56contract UniswapV2Factory is IUniswapV2Factory {7 address public feeTo;8 address public feeToSetter; 复制这些状态变量是执行协议费用所必需的(请见白皮书(opens in a new tab)的第 5 页)。 feeTo 地址用于累积流动性代币以收取协议费,而 feeToSetter 地址可用于将 feeTo 更改为不同地址。1 mapping(address => mapping(address => address)) public getPair;2 address[] public allPairs; 复制这些变量用以跟踪配对,即两种代币之间的兑换。第一个变量 getPair 是一个映射,它根据兑换的两种 ERC-20 代币来识别币对交易所合约。 ERC-20 代币通过实现它们的合约的地址来识别,因此关键字和值都是地址。 为了获取币对交易所地址,以便能够将 tokenA 兑换成 tokenB,可以使用 getPair [部署合约 | Uniswap V3 Book 中文版
| Uniswap V3 Book 中文版Uniswap V3 Book 中文版Milestone 0. 简介交易市场简介恒定函数做市商(CFMM)Uniswap V3开发环境Milestone 1. 第一笔交易简介计算流动性提供流动性第一笔交易管理合约部署合约用户界面Milestone 2. 第二笔交易简介输出金额计算Solidity中的数学运算Tick Bitmap Index通用mint通用swap报价合约用户界面Milestone 3. 跨tick交易简介不同价格区间跨tick交易滑点保护流动性计算关于定点数的拓展闪电贷用户界面Milestone 4. 多池子交易简介工厂合约交易路径多池子交易用户界面Tick 舍入Milestone 5. 费率和价格预言机简介交易费率闪电贷费率协议费率价格预言机用户界面Milestone 6: NFT positions简介ERC721 概述NFT 管理员合约NFT 渲染器补充资料中英名词对照
部署合约
部署选择本地网络运行本地区块链第一次部署与合约交互,ABIToken余额现在的 tick 和价格ABI
\[ \]部署
#我们的第一版合约已经完成了。现在,让我们来看看如何把它部署在一个本地以太坊网络上,方便我们之后用前端 app 交互。选择本地网络
#智能合约的开发需要运行一个本地的网络来在开发过程中进行部署和测试。这样的网络需要具有以下特点:真实的区块链。它必须是一个真实的区块链网络而不是一个模拟器,我们希望我们的合约如果能够在这样的网络上正常工作,那也一定能在主网上正常工作;速度。我们希望我们的交易能够快速被执行,这样我们能快速迭代;以太币。为了支付gas费,我们需要一些eth,因此我们希望这个网络能够允许我们生成任意数量的eth;cheat code。除了提供标准的 API,我们还希望这个网络能让我们做更多的事,例如:在任何地址上部署合约,以任何地址执行交易,直接修改合约状态等等。今天,有许多的工具能够提供这样的功能:Truffle套件中的GanacheHardhat,一套智能合约开发环境,除了包含本地网络节点以外还有很多有用的工具Foundry中的Anvil所有这些解决方案都能够满足我们的需求。尽管如此,项目现在都逐渐从 Ganache(最早的解决方案)迁移到 Hardhat(目前使用最广的方案),而 Foundry 也成为开发者的新宠。Foundry 也是上述三个方案中唯一使用 Solidity 来编写测试的框架(其他框架都使用 JavaScript)。除此以外,Foundry 还允许使用 Solidity 来编写部署脚本。因此,由于我们想一直使用 Solidity,我们会使用 Anvil 来运行一个本地区块链,并且使用 Solidity 编写部署脚本。运行本地区块链
#Anvil 不需要进行配置,我们可以直接在命令行运行:$ anvil
_ _
(_) | |
__ _ _ __ __ __ _ | |
/ _` | | '_ \ \ \ / / | | | |
| (_| | | | | | \ V / | | | |
\__,_| |_| |_| \_/ |_| |_|
0.1.0 (d89f6af 2022-06-24T00:15:17.897682Z)
https://github.com/foundry-rs/foundry
...
Listening on 127.0.0.1:8545
Anvil运行一个以太坊节点,所以它实际上并不是个网络,但也没什么问题。默认配置下它会创建 10 个账户,每个有 10000 ETH。它会把这些账户和对应私钥打印在命令行,我们会使用其中一个来部署合约和与其交互。Anvil在 127.0.0.1:8545 开放了 JSON-RPC API 接口——这个接口是与以太坊节点交互的主要方式。你可以在这里找到完整的 API 文档。在这里,你可以用 curl 与其交互:$ curl -X POST -H 'Content-Type: application/json' \
--data '{"id":1,"jsonrpc":"2.0","method":"eth_chainId"}' \
http://127.0.0.1:8545
{"jsonrpc":"2.0","id":1,"result":"0x7a69"}
$ curl -X POST -H 'Content-Type: application/json' \
--data '{"id":1,"jsonrpc":"2.0","method":"eth_getBalance","params":["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","latest"]}' \
http://127.0.0.1:8545
{"jsonrpc":"2.0","id":1,"result":"0x21e19e0c9bab2400000"}
你也可以使用 cast(foundry 中的另一个组件)来访问:$ cast chain-id
31337
$ cast balance 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
10000000000000000000000
现在,我们来将池子合约和管理合约都部署在本地网络上。第一次部署
#根本上来讲,部署一个合约意味着:将源代码编译成 EVM 字节码发送一个包含这些字节码的交易新建一个地址,执行字节码中构造函数的部分,将初始化的字节码存放在该地址。这一步是由以太坊节点自动完成的,在这笔交易被打包上链时。部署通常包含很多个步骤:准备参数,部署辅助合约,部署主合约,初始化合约等等。脚本能帮助我们自动化完成这些步骤,并且我们现在能用 Solidity 来编写脚本!创建 scripts/DeployDevelopment.sol 文件,写入以下内容:// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.14;
import "forge-std/Script.sol";
contract DeployDevelopment is Script {
function run() public {
...
}
}
它看起来与测试合约十分相似,唯一的差别在于它继承自 Script 合约而不是 Test。并且按照惯例,我们需要定义一个 run 函数作为部署脚本的主体。在 run 函数中,我们首先定义部署需要的参数:uint256 wethBalance = 1 ether;
uint256 usdcBalance = 5042 ether;
int24 currentTick = 85176;
uint160 currentSqrtP = 5602277097478614198912276234240;
这些正是我们之前用过的值。这里我们需要铸造 5042 个 USDC——其中 5000 个用来提供流动性,42 个用来交易。接下来,我们定义一系列在部署过程中需要执行的交易(每一步都是独立的交易)。我们使用startBroadcast/endBroadcast这个 cheat code:vm.startBroadcast();
...
vm.stopBroadcast();
这些 cheat code 可以在 forge的文档中找到,我们是从继承的 forge-std/Script.sol 里面得到这些功能的。在 broadcast() cheat code后面,或者 startBroadcast()/stopBroadcast() 之间的所有语句都会被转化成交易,这些交易会被发送到执行这个脚本的节点。在这两个 cheat code 之间,我们开始真正的部署步骤。首先需要部署两种 token:ERC20Mintable token0 = new ERC20Mintable("Wrapped Ether", "WETH", 18);
ERC20Mintable token1 = new ERC20Mintable("USD Coin", "USDC", 18);
没有 token 我们就无法部署池子,因此需要先部署 token 合约。
(译者注:由于原生代币 ETH 没有 approve 功能,因此在这里使用的是 WETH。在各种金融协议中,原生 ETH 通常都被单独拿出来处理)由于我们是在本地网络上进行部署,我们需要自行部署对应的 token。在主网上和公开测试网上(Ropsten, Goerli, Sepolia),这些 token 已经被部署。因此,如果想要在这些网络上进行部署,我们需要写网络特定的部署脚本。接下来就是部署池子合约:UniswapV3Pool pool = new UniswapV3Pool(
address(token0),
address(token1),
currentSqrtP,
currentTick
);
然后部署管理合约:UniswapV3Manager manager = new UniswapV3Manager();
最后,我们给我们自己的地址铸造一些token用来之后交易:token0.mint(msg.sender, wethBalance);
token1.mint(msg.sender, usdcBalance);
在Foundry脚本中,msg.sender 是在 broadcast 块中发送交易的地址。我们可以在运行脚本的时候对其进行设置。在脚本的最后,使用 console.log 打印出对应的地址信息:console.log("WETH address", address(token0));
console.log("USDC address", address(token1));
console.log("Pool address", address(pool));
console.log("Manager address", address(manager));
现在我们来运行这个脚本(确保 Anvil 在另一个窗口中正在运行):$ forge script scripts/DeployDevelopment.s.sol --broadcast --fork-url http://localhost:8545 --private-key $PRIVATE_KEY
--broadcast 启动交易的广播。它并不是默认开启的因为并不是每一个脚本都需要发送交易。--fork-url 设置我们要发送交易的节点地址。--private-key 设置调用者的钱包:需要私钥来签名交易。我们可以选择之前 Anvil 启动时在命令行打印出来的任何一个私钥。作者选择了第一个地址和私钥。部署需要消耗几秒钟。最后,你会看到它发送了一系列的交易。它同时也将交易收据存在了 broadcast 文件夹。在 Anvil 运行的窗口里,你也能看到很多行例如 eth_sendRawTransaction, eth_getTransactionByHash,
和 eth_getTransactionReceipt 这样的信息——在你向 Anvil 发送交易后,Forge 使用 JSON-RPC API 来检查它们的状态并且获得交易执行的结果(收据)。恭喜!你刚刚成功部署了一个智能合约!与合约交互,ABI
#现在,让我们来看看我们如何与已经部署的合约交互。每个合约都由一系列的public函数开放。以池子合约为例,包含 mint(...) 和 swap(...)。除此之外,Solidity 为每一个 public 的变量创建了 getter,所以我们也可以调用 token0(),token1(),positions() 等等,来访问对应变量的值。然而,由于合约都被编译成字节码,函数名在编译过程中丢失并且不存储在区块链上。在链上,每个函数都用一个函数选择器(selector)来表示,即函数签名哈希的前 4 字节。伪代码为:hash("transfer(address,address,uint256)")[0:4]
EVM 使用的是 Keccak 哈希算法,标准化名字为 SHA-3。特别地,Solidity 中的哈希函数名字为keccak256。根据以上信息,我们展示两种对于部署的合约进行调用的方法:一种使用 curl 来进行底层的调用,一种使用 cast。Token余额
#我们来检查一下部署者地址种的WETH余额。这个函数的签名是 balanceOf(address)(在 ERC-20 标准中定义),为了计算这个函数的 ID(即选择器),我们计算哈希并取前 4 字节:$ cast keccak "balanceOf(address)"| cut -b 1-10
0x70a08231
要把地址作为参数传递进去,我们把它附在函数选择器的后面(在左边 padding 到 32 个字节,因为地址在函数调用中占 32 字节):0x70a08231000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 是我们要查看余额的地址。这是本书选择的部署者的地址,也是 Anvil 创建时的第一个地址。接下来,我们会使用 eth_call JSON-RPC 方法来进行这个调用。注意到,这一步不需要发送一个交易——我们仅仅是从合约中读取数据。$ params='{"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0xe7f1725e7734ce288f8367e1bb143e90bb3f0512","data":"0x70a08231000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"}'
$ curl -X POST -H 'Content-Type: application/json' \
--data '{"id":1,"jsonrpc":"2.0","method":"eth_call","params":['"$params"',"latest"]}' \
http://127.0.0.1:8545
{"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000011153ce5e56cf880000"}
“to” 地址是 USDC token 的地址,它是在上一步部署脚本运行时打印出的日志里面的地址。以太坊节点返回的结果是字节流,为了理解结果我们需要知道返回值的类型。在 balanceOf 函数中,返回值的类型是 uint256。用 cast 可以把结果转换成十进制然后转换成 ethers 单位:$ cast --to-dec 0x00000000000000000000000000000000000000000000011153ce5e56cf880000| cast --from-wei
5042.000000000000000000
余额是正确的,我们在自己的地址中有 5042 USDC。现在的 tick 和价格
#上述例子展示了底层的合约调用。通常来说,你永远不会使用 curl 来发起调用,而是使用某个更友好的工具或库。cast 在这里也能够帮助我们简化这一过程。我们来使用 cast 获得当前合约的 tick 和 price:$ cast call POOL_ADDRESS "slot0()"| xargs cast --abi-decode "a()(uint160,int24)"
5602277097478614198912276234240
85176
如此容易!第一个值就是我们的 $\sqrt{P}$,第二个值就是现在的 tick。由于 --abi-decode 需要完整的函数签名,我们必须声明一个函数名 “a()",尽管我们只是为了解码函数的输出。ABI
#为了简化与合约的交易,Solidity 编译器可以输出 ABI,Application Binary Interface(应用二进制接口)。ABI 是一个包含了合约中所有 public 方法和事件的 JSON 文件。文件的目的在于使得编码函数参数和解码函数输出都更加容易。我们可以通过 Forge 来获取 ABI:$ forge inspect UniswapV3Pool abi
可以看一看生成的文件,来更好地理解 ABI 意味着什么。部署选择本地网络运行本地区块链第一次部署与合约交互,ABIToken余额现在的 tick 和价格
[Uniswap v2 合约代码解析] Uniswap v2 Pair合约代码 - 知乎
[Uniswap v2 合约代码解析] Uniswap v2 Pair合约代码 - 知乎切换模式写文章登录/注册[Uniswap v2 合约代码解析] Uniswap v2 Pair合约代码落花1. 概述Uniswap作为一个去中心化的交易所,它舍弃了传统订单薄的撮合方式,采用流动池加恒定乘积公式算法( x*y=k )为不同加密资产提供即时报价和兑换服务。Uniswap会为每一对token生成一个交易对,每一个交易对就是UniswapV2Pair部署上去产生的。UniswapV2Pair合约在Uniswap V2的核心合约代码库中,本文将会参照v2的白皮书来解读UniswapV2Pair合约。 2. UniswapV2Pair合约继承关系UniswapV2Pair合约有一个接口合约IUniswapV2Pair定义了UniswapV2Pair所要实现的方法,除此之外还继承了之前文章所分析的UniswapV2ERC20合约,这就说明这个Pair合约实际上就是一个token。3. UniswapV2Pair合约代码解析3.1 类型方法增强首先是将SafeMath和UQ112x112的库方法给到对应的类型上。using SafeMath for uint;
using UQ112x112 for uint224;
为什么要赋予unit224类型新的方法?这是因为在Solidity里面没有非整型的类型,但是token的数量肯定会出现小数位,使用UQ112x112的库去模拟浮点数类型。将unit224中的112位当作浮点数的整数部分,另外112位当作浮点数的小数部分,这样的话其精度可以达到 \frac{1}{2^{112}} , 剩余的32位主要是存放blockTimestampLast变量,该变量的作用以及更新会在后面讲到。library UQ112x112 {
uint224 constant Q112 = 2**112;
// encode a uint112 as a UQ112x112
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // never overflows
}
// divide a UQ112x112 by a uint112, returning a UQ112x112
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
3.2 常量最小流动性定义 最小流动性的定义是1000,具体细节我会在后面铸币方法的解析中提到,为什么是1000在白皮书中也提到了,具体细节可以查看v2的白皮书中的3.4节。uint public constant MINIMUM_LIQUIDITY = 10**3;
SELECTORSELECTOR常量值为transfer(address,unit256)字符串哈希值的前4个字节,这个用于直接使用call方法调用token的转账方法。bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
工厂地址 因为pair合约是通过工厂合约进行部署的,所有会有一个变量专门存放工厂合约的地址。address public factory;
token地址 pair合约的含义,就是一对token,所有在合约中会存放两个token的地址,便于调用。address public token0;
address public token1;
储备量相关储备量是当前pair合约所持有的token数量,blockTimestampLast主要用于判断是不是区块的第一笔交易。reserve0、reserve1和blockTimestampLast三者的位数加起来正好是unit的位数。uint112 private reserve0;
uint112 private reserve1;
uint32 private blockTimestampLast;
价格最后累计这里的价格最后累计,是用于Uniswap v2所提供的价格预言机上,该数值会在每个区块的第一笔交易进行更新。uint public price0CumulativeLast;
uint public price1CumulativeLast;
k值kLast这个变量在没有开启收费的时候,是等于0的,只有当开启平台收费的时候,这个值才等于k值,因为一般开启平台收费,那么k值就不会一直等于两个储备量向乘的结果来。uint public kLast;
3.3 事件铸造事件event Mint(address indexed sender, uint amount0, uint amount1);
销毁事件event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
交换事件event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
同步事件event Sync(uint112 reserve0, uint112 reserve1);
3.4 修饰方法在pair合约里面定义了一个修饰方法,主要是防止重入攻击的。// 锁定变量,防止重入
uint private unlocked = 1;
/*
* @dev 修饰方法,锁定运行防止重入
*/
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
3.5 方法3.5.1 构造方法构造方法比较简单,直接给factory变量赋予msg.sender的值,为什么要这样赋值,因为pair合约是通过factory合约进行部署的,所以msg.sender的值就等于工厂合约的地址。constructor() public {
factory = msg.sender;
}
3.5.2 初始化方法initialze方法是Solidity中一个比较特殊的方法,它仅仅只有在合约创建之后调用一次,为什么使用initialze方法初始化pair合约而不是在构造函数中初始化,这是因为pair合约是通过create2部署的,create2部署合约的特点就在于部署合约的地址是可预测的,并且后一次部署的合约可以把前一次部署的合约给覆盖,这样可以实现合约的升级。如果想要实现升级,就需要构造函数不能有任何参数,这样才能让每次部署的地址都保持一致,具体细节可以查看create2的文档。 在这个initialize方法中,主要是将两个token的地址分别赋予。function initialize(address _token0, address _token1) external {
// 确认调用者为工厂地址
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}
3.5.3 外部调用方法获取储备量方法返回三个信息,token0的储备量,token1的储备量,blockTimestampLast:上一个区块的时间戳。function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
铸币方法mint函数的输入为一个地址to,输出为该地址所提供的流动性,在Uniswap中,流动性也被体现成token即LP token。 铸币流程发生在router合约向pair合约发送代币之后,因此此次的储备量和合约的token余额是不相等的,中间的差值就是需要铸币的token金额,即amount0和amount1。然后是收取平台手续费,这部分代码会在后面进行解释。然后获取总的流动性的供应量totoalSupply,如果totalSupply等于0的话,就代表是首次铸币。 在Uniswap V2中,首次铸币获取的流动性由以下公式得到s_{minted} = \sqrt {x_{deposited} * y_{deposited}} = \sqrt {k} 这里的k就是恒定乘积公式中的k。这个公式确保了在任意时刻添加流动性时,LP token的价值与初始供应的tokenA和tokenB的比例无关,比如在最开始的时候,一个ABC的价格等于100个XYZ的价格,有人存入2个ABC和200个XYZ,这个时候他会获得20个LP token,这20个LP token的价值为2个ABC加上200个XYZ,如果有人在最开始的时候存入2个ABC和800个XYZ,这个时候他会获得40个LP token。但是我们可以看到代码里面,当第一个人存入token的时候,并没有完全获得对应数量的LP token,有MININUM_LIQUIDITY数量的LP token被转入0地址销毁了。这里是为了防止有人刻意抬高流动性单价从而垄断交易对,使得散户无力参与,即无法停供流动性。 具体的攻击流程如下: 1. 首先发送小额toekn(比如1 wei)到交易对并且mint(),获得价值1 wei的LP token,此时的totalSupply等于1 wei, reserve0和reserve也为1 wei。 2. 然后发送大额token(比如2000 ether)到交易对,但不调用mint(),而是直接调用sync(),此时totalSupply为1 wei, reserve0和reserve1分别为1 wei + 2000 ether。 3. 这个时候,1 wei的LP token的价值就约等于2000 ether了,后来的人再想获得1 wei的流动性,就需要付出价值2000 ether的token了。因此为了防止这种攻击的发生,只有去限制流动性的下限,在首次铸币的时候,会要求铸币者提供大于MININUM_LIQUIDITY数量的流动性,否则就无法注入流动性。并且为了防止攻击者通过burn()将流动性销毁,导致总流动性不低于MININUM_LIQUIDIY的限制被绕过,代码直接在第一次铸币的时候就把首次铸币者应该获得的流动性扣除了MININUM_LIQUIDITY数量的LP token发送到address(0)锁住,依次达到限制。然后我们接着看如果不是第一次铸币的时候,流动性的获取公式如下:s_{minted} = min(amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1) 根据以上公式,流动性的获取会根据存入的两种token分别计算,然后取最小的那个。之后就判断流动性是否大于0,如果不大于0,就revert整个流程。之后就是将LP token 给到address(to), 更新储备量, 更新储备量这个方法我们会在后面进行解析。之后如果开启了平台手续费收取,那么就会在这里重新计算k值。最后触发铸造事件。/*
* @dev 铸造方法
* @param to to地址
* @return liquidity 流动性
* @notice 应该从执行重要安全检查的合约中调用此底层方法
* this low-level function should be called from a contract which performs important safety checks
*/
function mint(address to) external lock returns (uint liquidity) {
// 获取储备量0和储备量1
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 获取当前合约在token0合约内的余额
uint balance0 = IERC20(token0).balanceOf(address(this));
// 获取当前合约在token1合约内的余额
uint balance1 = IERC20(token1).balanceOf(address(this));
// amount0 = 余额0 - 储备0
uint amount0 = balance0.sub(_reserve0);
// amount1 = 余额1 - 储备1
uint amount1 = balance1.sub(_reserve1);
// 返回铸造费开关
bool feeOn = _mintFee(_reserve0, _reserve1);
// 获取totalSupply
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// 如果_totalSupply等于0
if (_totalSupply == 0) {
// 流动性 = (数量0 * 数量1)的平方根 - 最小流动性1000
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
// 在总量为0的初始状态,永久锁定最低流动性
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
} else {
// 流动性 = 最小值(amount0 * _totalSupply / _reserve0 和 (amount1 * _totalSupply) / reserve1)
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
// 确认流动性 > 0
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
// 铸造流动性给to地址
_mint(to, liquidity);
// 更新储备量
_update(balance0, balance1, _reserve0, _reserve1);
// 如果铸造费开关为true,k值 = 储备0 * 储备1
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
// 触发铸造事件
emit Mint(msg.sender, amount0, amount1);
}
销毁流动性方法如果流动性的提供者想要收回流动性,那么就需要调用该方法。首先通过getReserves()方法获取现在的储备量。然后获取token0和token1在当前pair合约的余额然后从当前合约的balanceOf获取要销毁的流动性金额,这里为什么是从自身获取,是因为当前合约的余额是流动性提供者通过路由合约发送到pair合约要销毁的金额。计算平台手续费,这部分代码在后面解析。获取totalSupply,然后计算流动性提供者可以取出的token0和token1的数量,数量分别为amount0和amount1。直接通过如下公式计算得到amount0 = liquidity * balance0 / totalSupply amount1 = liquidity * balance1 / totalSUpply 实际上,上述公式可以转换成如下公式,取出来token的数量与持有的流动性占总流动性的比例有关,这样可以将流动性提供者在存入流动性期间所获取的流动性挖矿的收益也取出。amount0 / balance0 = liquidity / totalSupply amount1 / balance1 = liquidity / totalSupply 然后确保取出的amount大于0销毁合约内的流动性数量,发送token给address(to), 更新储备量。如果有平台手续费收取的话,重新计算k值。发送销毁代币事件。/*
* @dev 销毁方法
* @param to to地址
* @return amount0
* @return amount1
* @notice 应该从执行重要安全检查的合同中调用此低级功能
* this low-level function should be called from a contract which performs important safety checks
*/
function burn(address to) external lock returns (uint amount0, uint amount1) {
// 获取储备量0,储备量1
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 带入变量
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
// 获取当前合约在token0合约内的余额
uint balance0 = IERC20(_token0).balanceOf(address(this));
// 获取当前合约在token1合约内的余额
uint balance1 = IERC20(_token1).balanceOf(address(this));
// 从当前合约的balanceOf映射中获取当前合约自身流动性数量
// 当前合约的余额是用户通过路由合约发送到pair合约要销毁的金额
uint liquidity = balanceOf[address(this)];
// 返回铸造费开关
bool feeOn = _mintFee(_reserve0, _reserve1);
// 获取totalSupply
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
// amount0和amount1是用户能取出来多少的数额
// amount0 = 流动性数量 * 余额0 / totalSupply 使用余额确保按比例分配
// 取出来的时候包含了很多个千分之三的手续费
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
// amount1 = 流动性数量 * 余额1 / totalSupply 使用余额确保按比例分配
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
// 确认amount0和amount1都大于0
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
// 销毁当前合约内的流动性数量
_burn(address(this), liquidity);
// 将amount0数量的_token0发送给to地址
_safeTransfer(_token0, to, amount0);
// 将amount1数量的_toekn1发给to地址
_safeTransfer(_token1, to, amount1);
// 更新balance0和balance1
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}
交换token方法交换token方法一般通过路由合约调用,功能是交换token,需要的参数包括:amount0Out:token0要交换出的数额;amount1Out:token1要交换出的数额,to:交换token要发到的地址,一般是其它pair合约地址;data用于闪电贷回调使用。首先确认amount0Out或者amount1Out有一个大于0,然后确保储备量大于要取出的金额。然后确保address(to)不等于对应的token地址。然后发送token到对应的地址上。然后data有数据,就执行闪电贷的调用。之后获取两个token的余额,判断是否在交换之间,有token的输入,如果没有输入就revert。如果有输入,还需要保证交换之后的储备量的乘积等于k,具体代码中计算公式如下:(balance0 * 1000 - amount0In * 3) * (balance1 * 1000 - amount1In * 2) = (reserve0 * reserve1) * 1000^2 代码中的公式这样的原因还是因为Solidity不支持小数运算,上述公式可以改写成如下形态:(balance0 - amount0In * 0.003) * (balance1 - amount1In * 0.003) = reserve0 * reserve1 其中 reserve0 * reserve1相当于k值。这个公式就对应白皮书所写的每次交易收取千分之三的手续费。最后更新储备量,触发交换事件。/*
* @dev 交换方法
* @param amount0Out 输出数额0
* @param amount1Out 输出数额1
* @param to to地址
* @param data 用于回调的数据
* @notice 应该从执行重要安全检查的合同中调用此低级功能
* this low-level function should be called from a contract which performs important safety checks
*/
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
// 确认amount0Out和amount1Out都大于0
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
// 获取储备量0和储备量1
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
// 确认取出的量不能大于它的 储备量
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
// 初始化变量
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
// 标记_toekn{0,1}的作用域,避免堆栈太深
address _token0 = token0;
address _token1 = token1;
// 确保to地址不等于token0和token1的地址
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
// 发送token0代币
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
// 发送token1代币
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
// 如果data的长度大于0,调用to地址的接口
// 闪电贷
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
// 余额0,1 = 当前合约在token0,1合约内的余额
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
// 如果余额0 > 大于储备0 - amount0Out 则 amount0In = 余额0 - (储备0 - amount0Out) 否则amount0In = 0
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
// 确保输入数量0||1大于0
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
// 调整后的余额0 = 余额0 * 1000 - (amount0In * 3)
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
// 调整后的余额1 = 余额1 * 1000 - (amount1In * 3)
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
// 确保balance0Adjusted * balance1Adjusted >= 储备0 * 储备1 * 1000000
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
// 更新储备量
_update(balance0, balance1, _reserve0, _reserve1);
// 触发交换事件
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
skim方法skim方法的功能是强制让余额等于储备量,一般用于储备量溢出的情况下,将多余的余额转出到address(to)上,使余额重新等于储备量。具体的逻辑也非常简单,就是将余额减去储备量的token发送到address(t0)上/*
* @dev 强制平衡以匹配储备,按照储备量匹配余额
* @param to to地址
force balances to match reserves
*/
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
// 将当前合约在token1,2的余额-储备量0,1安全发送到to地址上
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
sync方法 刚才skim方法是强制让余额与储备量对等,sync方法则是强制让储备量与余额对等,直接调用就是更新储备量的私有方法。/*
* @dev 强制准备金与余额匹配,按照余额匹配储备量
* force reserves to match balances
*/
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
3.5.4 私有方法私有转账方法 该方法实现了只知道token合约地址就可以直接调用transfer方法的功能,具体实现如下,这个方法传入了三个参数,分别是token:合约的地址,to:要转账的地址,value:要转账的金额。然后直接使用call方法直接调用对应token合约的transfer方法,获取返回值,需要判断返回值为true并且返回的data长度为0或者解码后为true。使用call方法的优势在于可以在不知道token合约具体代码的前提下调用其方法。/*
* @dev 私有安全发送
* @param token token地址
* @param to to地址
* @param value 数额
*/
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
私有更新储备量方法私有更新储备量方法主要用于每次添加流动性或者减少流动性之后调用,用于将余额同步给储备量。并且会判断时间流逝,在每一个区块的第一次调用时候,更新价格累加器,用于Uniswap v2的价格预言机。在方法最开始的时候,会判断余额会不会导致储备量溢出,如果溢出的话,就revert,这个时候就需要有人从外部调用skim方法,修正溢出,将多出的token转出。在这个方法里面除了更新reserve0和reserve1之外,还更新了blockTimestampLast,它的取值是当前区块时间对 2^{32} 取余得到的结果。/*
* @dev 更新储备量,并在每一个区块的第一次调用时,更新价格累加器
* @param balance0 余额0
* @param balance1 余额1
* @param _reserve0 储备量0
* @param _reserve1 储备量1
* update reserves and, on the first call per block, price accumulators
*/
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
// 确认余额0和余额1小于等于最大的uint112
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
// 区块时间戳,将时间戳转换成uint32
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
// 计算时间流逝
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
// 如果时间流逝>0,并且储备量0、1不等于0,也就是第一个调用
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
// 价格0最后累计 += 储备量1 * 2**112 / 储备量0 * 时间流逝
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
// 价格1最后累计 += 储备量0 * 2**112 / 储备量1 * 时间流逝
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
// 余额0,1放入储备量0,1
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
// 更新最后时间戳
blockTimestampLast = blockTimestamp;
// 触发同步事件
emit Sync(reserve0, reserve1);
}
平台手续费收取方法 平台手续费的是否开启的开关是在factory合约中定义的,后续会解析factory合约。如果收取平台收费的话,那么收取的金额为交易手续费的1/6。具体计算方式可以去参考白皮书。_mintFee函数首先获取factory合约里面的address(feeTo), 这是平台收取手续费的地址。然后判断address(feeTo)是否等于address(0)决定是否收取平台手续费。如果开启平台手续费收取,首先会判断kLast是否为0,如果不为0就进行平台手续费的计算。平台手续费的计算,首先要明确一点,因为每一笔交易都会有千分之三的手续费,那么k值也会随着缓慢增加,所有连续两个时刻之间的k值差值就是这段时间的手续费。以上过程可以用如下公式表达:f_{1, 2} = \frac{\sqrt{k_{2}} - \sqrt{k_{1}}}{\sqrt{k_{2}}} 其中 f_{1, 2} 表示当前时刻的平台手续费占当前时刻流动性的比重, {k_1} 和 {k_2}分别表示前一时刻流动性的值和当前时刻流动性的值。此时假设当前时刻的LP token的总供应量是 s_{1},需要发放给address(feeTo)的LP token的量为 s_{m}, 由于LP token不能减发,且发送到address(feeTo)的LP token需要在发送给流动性提供者LP token之前发放,故而需要满足如下等式:\frac{s_{m}}{s_{m} + s_{1}} = \frac{1}{6} * f_{1, 2} 其中 \frac{1}{6} 是平台手续费占交易手续费的比例。由上述两式可推算出 s_{m} 的结果如下:s_{m} = \frac{\sqrt{k_{2}} - \sqrt{k_{1}}}{5 * \sqrt{k_{2}} + \sqrt{k_{1}}} * s_{1} 上述式子,就等价于如下代码中计算平台手续费的过程。/*
* @dev 如果收费,铸造流动性相当于1/6的增长sqrt(k)
* @param _reserve0 储备量0
* @param _reserve1 储备量1
* @return feeOn
* 这一部分可参考白皮书协议费用那部分·
* if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
*/
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
// 查询工厂合约的feeTo变量值
address feeTo = IUniswapV2Factory(factory).feeTo();
// 如果feeTo不等于0地址,feeOn等于true否则false
feeOn = feeTo != address(0);
// 定义k值
uint _kLast = kLast; // gas savings
// 如果feeOn等于true
if (feeOn) {
// 如果k值不等于0
if (_kLast != 0) {
// 计算(_reserve0*_reserve1)的平方根
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
// 计算k值的平方根
uint rootKLast = Math.sqrt(_kLast);
// 如果rootK>rootKLast
if (rootK > rootKLast) {
// 分子 = erc20总量 * (rootK - rootKLast)
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
// 分母 = rootK * 5 + rootKLast
uint denominator = rootK.mul(5).add(rootKLast);
// 流动性 = 分子 / 分母
uint liquidity = numerator / denominator;
// 如果流动性 > 0 将流动性铸造给feeTo地址
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
// 否则如果_kLast不等于0
} else if (_kLast != 0) {
kLast = 0;
}}
4. 总结本篇文章解析了Uniswap V2核心合约代码中的pair合约的代码,在实际部署当作pair合约不会直接进行部署,而是通过factory合约进行部署的,接下来会继续解析factory合约的代码。5. Refenceshttps://learnblockchain.cn/article/3004https://learnblockchain.cn/article/2753https://learnblockchain.cn/article/1448发布于 2022-01-09 16:00区块链(Blockchain)去中心化应用(DApp)Uniswap赞同 19添加评论分享喜欢收藏申请
UniSwap学习笔记1: 概览以及交易对地址计算-腾讯云开发者社区-腾讯云
wap学习笔记1: 概览以及交易对地址计算-腾讯云开发者社区-腾讯云Tiny熊UniSwap学习笔记1: 概览以及交易对地址计算关注作者腾讯云开发者社区文档建议反馈控制台首页学习活动专区工具TVP最新优惠活动文章/答案/技术大牛搜索搜索关闭发布登录/注册首页学习活动专区工具TVP最新优惠活动返回腾讯云官网Tiny熊首页学习活动专区工具TVP最新优惠活动返回腾讯云官网社区首页 >专栏 >UniSwap学习笔记1: 概览以及交易对地址计算UniSwap学习笔记1: 概览以及交易对地址计算Tiny熊关注发布于 2022-05-25 15:58:321.7K1发布于 2022-05-25 15:58:32举报文章被收录于专栏:深入浅出区块链技术深入浅出区块链技术本文作者:tony.ho[1]hello everyone, 我是 tony, 今天开始为大家分享我最近学习 Uniswap[2] 的小小心得, 希望各位看官多多提意见, 大家一起进步.Uniswap V2 代码地址:核心代码: 包括 Factory, Pair, WETH 3 个合约 https://github.com/Uniswap/v2-core外围代码: Router 合约 https://github.com/Uniswap/v2-periphery概览Uniswap core 有 3 个合约: Factory, Pair, WETH Uniswap periphery 有 1 个合约: RouterFactory 合约 (UniswapV2Factory.sol): 负责创建交易对, 保存所有交易对的地址 .Pair 合约 (UniswapV2Pair.sol) : 保存单个交易对的资金池信息, 包括每个交易对中两个 ERC20 代币的地址, 以及各自资金余额(reserve). 处理 mint, burn 和 交易(swap) 操作. Pair 合约是 Uniswap 代码的核心 UniswapV2Pair 本身也是 ERC20, 当添加流动性时会 mint 新的 pair 代币(pair token, or LP token), 删除流动性时会销毁一定数量的代币, 确保 pair token 的 totalSupply 始终等于池子中的两个交易对代币余额的几何平均值: totalSupply of pair token === sqrt(reserve1 * reserve2)WETH 合约: 这个合约是一个特殊的 ERC20, 它的目的是方便用户直接用 ETH 和 ERC20 进行交易, 你可以把 WETH 看作是一个全额担保的债券: 这个合约里面存储的 ETH 总是等于它发行的代币 WETH, (除非有人直接使用 WETH_address.transfer 向它发送了 ETH ). Pair 合约的交易对总是 ERC20 代币, 不能进行 ETH 和 ERC20 之间的交易, 所以 WETH 就被创造出来, 它有两个方法, deposit() 和 withdraw(), 前者是存入 ETH 获得 WETH, 后者是 销毁 WETH 获得 ETH. 但是这两个方法通常是由 Router 来调用的, 例如 Router 中的 swapExactETHForTokens 函数:function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
复制上面的代码中, 用户需要用 ETH 换成 ERC20 代币, 那么只能使用 WETH-ERC20 交易对, 用户将 ETH 发送给 Router, Router 获得 ETH 后用它换出 WETH ( 调用 WETH.deposit() ), 再将 WETH 发送给 Pair, 换出目标 ERC20 代币发送给参数 to 中指定的接收账户.Router 合约: (UniswapV2Router02.sol) 这个合约包含了几乎所有提供给应用层使用的函数 (除了 Factory 的 getPair 和 createPair), 包括 <添加,撤销流动性> 和 <交易>, 它和 Pair 的区别如下: Pair 中没有添加, 撤销流动性操作, 只有 mint 和 burn. Pair 中的 swap 操作是单个交易对的操作, 而 Router 的 swap 是可以给定路径, 进行一系列交易后得到最终目标代币 token1 -> token2 ... -> token_target 在此过程中, Router 会依次调用 (token_i, token_i+1) 所对应的交易对的 swap 函数, 并将换出的代币发送到 (token_i+1, token_i+2)交易对合约地址, 再次执行 swap, 发送... 直到换出最后的 token_target在后面的介绍中, 我们会阅读源码, 研究 swap 的流程细节.Pair 的创建 和 create2 的地址计算:交易对的创建是在 Factory 中完成, UniswapV2Factory.ceatePair 函数定义如下:function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
复制这里 Factory 使用了 create2 操作码部署新的 Pair 合约, 而不是 pair = new UniswapV2Pair(tokenA, tokenB):assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
复制这样做的好处是: 它可以得到一个确定的 pair 地址, 使得 Router 中就可以通过 tokenA, tokenB 计算出 pair 地址, 不再需要执行一次 Factory.getPair(tokenA, tokenB) 的跨合约调用, create2 生成地址的规则是:keccak256(abi.encodePacked(
hex'ff',
deployer_address, // 由Factory创建, 这里就是 factory合约地址
salt, // salt 是创建者自定义的数据, 避免重复, 这里的
// salt = keccak256(abi.encodePacked(token0, token1)), 且 token0 < token1
keccak256(type(Contract_to_Deploy).creationCode) // 此处 Contract_to_Deploy = UniswapV2Pair
))))
复制参考 periphery 的 libraries/UniswapV2Library.solfunction pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
))));
}
复制在这里, Router 为了节省 gas, 使用 pairFor 获取 tokenA 和 tokenB 的交易对地址, 而且最后的字节码哈希也是预先计算出来, 硬编码到代码里面.如果我们自己修改了 Pair 的源代码合约, 重新部署后, Router 就无法找到这个 Pair 地址了, 解决办法要么直接返回 factory.getPair(tokenA, tokenB), 要么就把以前部署的 Pair 全部舍弃, 重新部署 Factory 和所有 Pair, 再重新部署 Router.下面的 python 示例代码用于计算 pair 的 hash, 我们可以在重新部署 Pair 后调用这个函数, 再把 hash 复制到 UniswapV2Library.pairFor() 进行替换: 同时修改 UniswapV2Factory.sol , 增加两个函数用于测试:contract UniswapV2Factory is IUniswapV2Factory {
...
/*
* 返回 Pair的字节码
*/
function pair_code() public view returns(bytes memory){
return type(UniswapV2Pair).creationCode;
}
/*
* 返回 Pair的字节码哈希
*/
function pair_codehash() public view returns(bytes32 ){
return keccak256( type(UniswapV2Pair).creationCode );
}
...
}
复制以下是 python 代码, 用于计算 pair 的 code_hash, 并且模拟 create2 计算交易对地址, 如果与 factory.getPair 得到的地址相同, 证明我们的 code_hash 计算正确, 可以拷贝到 UniswapV2Library.sol 的 pairFor 函数###########################################################################################
#
# 重新编译并部署 pair 和 Factory 后, 调用此函数计算出hash, 放到 peripery/libraries/UniswapV2Library.sol 的
# 'pairFor' 函数, 替换原来的hash,
# 然后重新编译并部署Router
#
###########################################################################################
def calc_pair_address():
# 执行本函数前, 确保已经重新编译并部署 Factory 和 Pair, 同时部署测试代币 WBTC, USDT及交易对
# 执行完本函数后, 需要修改 Router源代码, 重新编译部署Router
abi, bytecode = _helper.get_contract_info(r"core/artifacts/UniswapV2Pair.json")
WBTC, USDT = _contracts["WBTC"], _contracts["USDT"]
factory = _contracts["Factory"]
pair_codehash = Web3.keccak(bytes.fromhex(bytecode))
addr1, addr2 = sorted((WBTC.address, USDT.address))
salt = Web3.solidityKeccak(['address' , 'address'], [addr1, addr2])
pair_address = Web3.solidityKeccak(['uint8', 'address' , 'bytes', 'bytes'],
[0xff,
factory.address,
salt,
pair_codehash])
print(f"pair code(python): 0x{bytecode}", )
evm_code = factory.functions.pair_code().call()
print(f"pair code (evm): 0x{evm_code.hex()}")
print(f"code hash(python): \n\t{pair_codehash.hex()}", )
evm_codehash = factory.functions.pair_codehash().call()
print(f"code hash(evm): \n\t0x{evm_codehash.hex()}")
print(f"pair address of WBTC-USDT(calculated from python): \n\t{pair_address.hex()}")
evm_pair_address = factory.functions.getPair(WBTC.address, USDT.address).call()
print(f"pair address of WBTC-USDT(in EVM create2): \n\t{evm_pair_address}")
复制得到以下结果, 证明我们的 python 端计算 pair 地址的方法是正确的:/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* pair code(python): 0x60806040526001600c5534801561001557600080fd5b50604051469080605261240d8239604080519182900360520182208282018252600a8352692ab734b9bbb0b8102b1960b11b6020938401528151808301835260018152603160f81b908401528151808401919091527fbfcc8ef98ffbf7b6c3fec7bf5185b566b9863e35a9d83acd49ad6824b5969738818301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6606082015260808101949094523060a0808601919091528151808603909101815260c09094019052825192019190912060035550600580546001600160a01b03191633179055612308806101056000396000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a714610534578063d505accf1461053c578063dd62ed3e1461058d578063fff6cae9146105bb576101a9565b8063ba9a7a56146104fe578063bc25cf7714610506578063c45a01551461052c576101a9565b80637ecebe00116100d35780637ecebe001461046557806389afcb441461048b57806395d89b41146104ca578063a9059cbb146104d2576101a9565b80636a6278421461041157806370a08231146104375780637464fc3d1461045d576101a9565b806323b872dd116101665780633644e515116101405780633644e515146103cb578063485cc955146103d35780635909c0d5146104015780635a3d549314610409576101a9565b806323b872dd1461036f57806330adf81f146103a5578063313ce567146103ad576101a9565b8063022c0d9f146101ae57806306fdde031461023c5780630902f1ac146102b9578063095ea7b3146102f15780630dfe16811461033157806318160ddd14610355575b600080fd5b61023a600480360360808110156101c457600080fd5b8135916020810135916001600160a01b0360408301351691908101906080810160608201356401000000008111156101fb57600080fd5b82018360208201111561020d57600080fd5b8035906020019184600183028401116401000000008311171561022f57600080fd5b5090925090506105c3565b005b610244610afe565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561027e578181015183820152602001610266565b50505050905090810190601f1680156102ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102c1610b24565b604080516001600160701b03948516815292909316602083015263ffffffff168183015290519081900360600190f35b61031d6004803603604081101561030757600080fd5b506001600160a01b038135169060200135610b4e565b604080519115158252519081900360200190f35b610339610b65565b604080516001600160a01b039092168252519081900360200190f35b61035d610b74565b60408051918252519081900360200190f35b61031d6004803603606081101561038557600080fd5b506001600160a01b03813581169160208101359091169060400135610b7a565b61035d610c76565b6103b5610c9a565b6040805160ff9092168252519081900360200190f35b61035d610c9f565b61023a600480360360408110156103e957600080fd5b506001600160a01b0381358116916020013516610ca5565b61035d610d29565b61035d610d2f565b61035d6004803603602081101561042757600080fd5b50356001600160a01b0316610d35565b61035d6004803603602081101561044d57600080fd5b50356001600160a01b0316611035565b61035d611047565b61035d6004803603602081101561047b57600080fd5b50356001600160a01b031661104d565b6104b1600480360360208110156104a157600080fd5b50356001600160a01b031661105f565b6040805192835260208301919091528051918290030190f35b610244611405565b61031d600480360360408110156104e857600080fd5b506001600160a01b038135169060200135611427565b61035d611434565b61023a6004803603602081101561051c57600080fd5b50356001600160a01b031661143a565b6103396115a5565b6103396115b4565b61023a600480360360e081101561055257600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c001356115c3565b61035d600480360360408110156105a357600080fd5b506001600160a01b03813581169160200135166117c5565b61023a6117e2565b600c5460011461060e576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55841515806106215750600084115b61065c5760405162461bcd60e51b81526004018080602001828103825260258152602001806121f56025913960400191505060405180910390fd5b600080610667610b24565b5091509150816001600160701b03168710801561068c5750806001600160701b031686105b6106c75760405162461bcd60e51b815260040180806020018281038252602181526020018061223e6021913960400191505060405180910390fd5b60065460075460009182916001600160a01b039182169190811690891682148015906107055750806001600160a01b0316896001600160a01b031614155b61074e576040805162461bcd60e51b8152602060048201526015602482015274556e697377617056323a20494e56414c49445f544f60581b604482015290519081900360640190fd5b8a1561075f5761075f828a8d611944565b891561077057610770818a8c611944565b861561082b57886001600160a01b03166310d1e85c338d8d8c8c6040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561081257600080fd5b505af1158015610826573d6000803e3d6000fd5b505050505b604080516370a0823160e01b815230600482015290516001600160a01b038416916370a08231916024808301926020929190829003018186803b15801561087157600080fd5b505afa158015610885573d6000803e3d6000fd5b505050506040513d602081101561089b57600080fd5b5051604080516370a0823160e01b815230600482015290519195506001600160a01b038316916370a0823191602480820192602092909190829003018186803b1580156108e757600080fd5b505afa1580156108fb573d6000803e3d6000fd5b505050506040513d602081101561091157600080fd5b5051925060009150506001600160701b0385168a90038311610934576000610943565b89856001600160701b03160383035b9050600089856001600160701b031603831161096057600061096f565b89856001600160701b03160383035b905060008211806109805750600081115b6109bb5760405162461bcd60e51b815260040180806020018281038252602481526020018061221a6024913960400191505060405180910390fd5b60006109ef6109d184600363ffffffff611ade16565b6109e3876103e863ffffffff611ade16565b9063ffffffff611b4116565b90506000610a076109d184600363ffffffff611ade16565b9050610a38620f4240610a2c6001600160701b038b8116908b1663ffffffff611ade16565b9063ffffffff611ade16565b610a48838363ffffffff611ade16565b1015610a8a576040805162461bcd60e51b815260206004820152600c60248201526b556e697377617056323a204b60a01b604482015290519081900360640190fd5b5050610a9884848888611b91565b60408051838152602081018390528082018d9052606081018c905290516001600160a01b038b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a8152602001692ab734b9bbb0b8102b1960b11b81525081565b6008546001600160701b0380821692600160701b830490911691600160e01b900463ffffffff1690565b6000610b5b338484611d56565b5060015b92915050565b6006546001600160a01b031681565b60005481565b6001600160a01b0383166000908152600260209081526040808320338452909152812054821115610bdc5760405162461bcd60e51b81526004018080602001828103825260258152602001806122af6025913960400191505060405180910390fd5b6001600160a01b038416600090815260026020908152604080832033845290915290205460001914610c61576001600160a01b0384166000908152600260209081526040808320338452909152902054610c3c908363ffffffff611b4116565b6001600160a01b03851660009081526002602090815260408083203384529091529020555b610c6c848484611db8565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b6005546001600160a01b03163314610cfb576040805162461bcd60e51b81526020600482015260146024820152732ab734b9bbb0b82b191d102327a92124a22222a760611b604482015290519081900360640190fd5b600680546001600160a01b039384166001600160a01b03199182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c54600114610d82576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c81905580610d92610b24565b50600654604080516370a0823160e01b815230600482015290519395509193506000926001600160a01b03909116916370a08231916024808301926020929190829003018186803b158015610de657600080fd5b505afa158015610dfa573d6000803e3d6000fd5b505050506040513d6020811015610e1057600080fd5b5051600754604080516370a0823160e01b815230600482015290519293506000926001600160a01b03909216916370a0823191602480820192602092909190829003018186803b158015610e6357600080fd5b505afa158015610e77573d6000803e3d6000fd5b505050506040513d6020811015610e8d57600080fd5b505190506000610eac836001600160701b03871663ffffffff611b4116565b90506000610ec9836001600160701b03871663ffffffff611b4116565b90506000610ed78787611e72565b60005490915080610f1457610f006103e86109e3610efb878763ffffffff611ade16565b611fd0565b9850610f0f60006103e8612022565b610f63565b610f606001600160701b038916610f31868463ffffffff611ade16565b81610f3857fe5b046001600160701b038916610f53868563ffffffff611ade16565b81610f5a57fe5b046120b8565b98505b60008911610fa25760405162461bcd60e51b81526004018080602001828103825260288152602001806122876028913960400191505060405180910390fd5b610fac8a8a612022565b610fb886868a8a611b91565b8115610fe857600854610fe4906001600160701b0380821691600160701b90041663ffffffff611ade16565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c546001146110ad576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c819055806110bd610b24565b50600654600754604080516370a0823160e01b815230600482015290519496509294506001600160a01b039182169391169160009184916370a08231916024808301926020929190829003018186803b15801561111957600080fd5b505afa15801561112d573d6000803e3d6000fd5b505050506040513d602081101561114357600080fd5b5051604080516370a0823160e01b815230600482015290519192506000916001600160a01b038516916370a08231916024808301926020929190829003018186803b15801561119157600080fd5b505afa1580156111a5573d6000803e3d6000fd5b505050506040513d60208110156111bb57600080fd5b5051306000908152600160205260408120549192506111da8888611e72565b600054909150806111f1848763ffffffff611ade16565b816111f857fe5b049a508061120c848663ffffffff611ade16565b8161121357fe5b04995060008b118015611226575060008a115b6112615760405162461bcd60e51b815260040180806020018281038252602881526020018061225f6028913960400191505060405180910390fd5b61126b30846120d0565b611276878d8d611944565b611281868d8c611944565b604080516370a0823160e01b815230600482015290516001600160a01b038916916370a08231916024808301926020929190829003018186803b1580156112c757600080fd5b505afa1580156112db573d6000803e3d6000fd5b505050506040513d60208110156112f157600080fd5b5051604080516370a0823160e01b815230600482015290519196506001600160a01b038816916370a0823191602480820192602092909190829003018186803b15801561133d57600080fd5b505afa158015611351573d6000803e3d6000fd5b505050506040513d602081101561136757600080fd5b5051935061137785858b8b611b91565b81156113a7576008546113a3906001600160701b0380821691600160701b90041663ffffffff611ade16565b600b555b604080518c8152602081018c905281516001600160a01b038f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b604051806040016040528060068152602001652aa72496ab1960d11b81525081565b6000610b5b338484611db8565b6103e881565b600c54600114611485576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55600654600754600854604080516370a0823160e01b815230600482015290516001600160a01b039485169490931692611534928592879261152f926001600160701b03169185916370a0823191602480820192602092909190829003018186803b1580156114f757600080fd5b505afa15801561150b573d6000803e3d6000fd5b505050506040513d602081101561152157600080fd5b50519063ffffffff611b4116565b611944565b600854604080516370a0823160e01b8152306004820152905161159b928492879261152f92600160701b90046001600160701b0316916001600160a01b038616916370a0823191602480820192602092909190829003018186803b1580156114f757600080fd5b50506001600c5550565b6005546001600160a01b031681565b6007546001600160a01b031681565b4284101561160d576040805162461bcd60e51b8152602060048201526012602482015271155b9a5cddd85c158c8e881156141254915160721b604482015290519081900360640190fd5b6003546001600160a01b0380891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e08501825280519083012061190160f01b6101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e280820193601f1981019281900390910190855afa158015611728573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381161580159061175e5750886001600160a01b0316816001600160a01b0316145b6117af576040805162461bcd60e51b815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b6117ba898989611d56565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c5460011461182d576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55600654604080516370a0823160e01b8152306004820152905161193d926001600160a01b0316916370a08231916024808301926020929190829003018186803b15801561187e57600080fd5b505afa158015611892573d6000803e3d6000fd5b505050506040513d60208110156118a857600080fd5b5051600754604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b1580156118f557600080fd5b505afa158015611909573d6000803e3d6000fd5b505050506040513d602081101561191f57600080fd5b50516008546001600160701b0380821691600160701b900416611b91565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e74323536290000000000000060209182015281516001600160a01b0385811660248301526044808301869052845180840390910181526064909201845291810180516001600160e01b031663a9059cbb60e01b1781529251815160009460609489169392918291908083835b602083106119f15780518252601f1990920191602091820191016119d2565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611a53576040519150601f19603f3d011682016040523d82523d6000602084013e611a58565b606091505b5091509150818015611a86575080511580611a865750808060200190516020811015611a8357600080fd5b50515b611ad7576040805162461bcd60e51b815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b6000811580611af957505080820282828281611af657fe5b04145b610b5f576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b604482015290519081900360640190fd5b80820382811115610b5f576040805162461bcd60e51b815260206004820152601560248201527464732d6d6174682d7375622d756e646572666c6f7760581b604482015290519081900360640190fd5b6001600160701b038411801590611baf57506001600160701b038311155b611bf6576040805162461bcd60e51b8152602060048201526013602482015272556e697377617056323a204f564552464c4f5760681b604482015290519081900360640190fd5b60085463ffffffff42811691600160e01b90048116820390811615801590611c2657506001600160701b03841615155b8015611c3a57506001600160701b03831615155b15611cab578063ffffffff16611c6885611c538661216e565b6001600160e01b03169063ffffffff61218016565b600980546001600160e01b03929092169290920201905563ffffffff8116611c9384611c538761216e565b600a80546001600160e01b0392909216929092020190555b600880546dffffffffffffffffffffffffffff19166001600160701b03888116919091176dffffffffffffffffffffffffffff60701b1916600160701b8883168102919091176001600160e01b0316600160e01b63ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b6001600160a01b03808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b038316600090815260016020526040902054611de1908263ffffffff611b4116565b6001600160a01b038085166000908152600160205260408082209390935590841681522054611e16908263ffffffff6121a516565b6001600160a01b0380841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a90046001600160a01b03166001600160a01b031663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b158015611ec357600080fd5b505afa158015611ed7573d6000803e3d6000fd5b505050506040513d6020811015611eed57600080fd5b5051600b546001600160a01b038216158015945091925090611fbc578015611fb7576000611f30610efb6001600160701b0388811690881663ffffffff611ade16565b90506000611f3d83611fd0565b905080821115611fb4576000611f6b611f5c848463ffffffff611b4116565b6000549063ffffffff611ade16565b90506000611f9083611f8486600563ffffffff611ade16565b9063ffffffff6121a516565b90506000818381611f9d57fe5b0490508015611fb057611fb08782612022565b5050505b50505b611fc8565b8015611fc8576000600b555b505092915050565b60006003821115612013575080600160028204015b8181101561200d57809150600281828581611ffc57fe5b04018161200557fe5b049050611fe5565b5061201d565b811561201d575060015b919050565b600054612035908263ffffffff6121a516565b60009081556001600160a01b038316815260016020526040902054612060908263ffffffff6121a516565b6001600160a01b03831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b60008183106120c757816120c9565b825b9392505050565b6001600160a01b0382166000908152600160205260409020546120f9908263ffffffff611b4116565b6001600160a01b03831660009081526001602052604081209190915554612126908263ffffffff611b4116565b60009081556040805183815290516001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6001600160701b0316600160701b0290565b60006001600160701b0382166001600160e01b0384168161219d57fe5b049392505050565b80820182811015610b5f576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544556e6973776170563245524332303a206e6f7420656e6f75676820616c6c6f77616e63652ea265627a7a72315820bf165e44001b5284076af3e56685540f3d6256d9ddeae5a8f22ed60b1397a80064736f6c63430005100032454950373132446f6d61696e28737472696e67206e616d652c737472696e672076657273696f6e2c75696e7432353620636861696e49642c6164647265737320766572696679696e67436f6e747261637429
*/
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释
* pair code (evm): 0x60806040526001600c5534801561001557600080fd5b50604051469080605261240d8239604080519182900360520182208282018252600a8352692ab734b9bbb0b8102b1960b11b6020938401528151808301835260018152603160f81b908401528151808401919091527fbfcc8ef98ffbf7b6c3fec7bf5185b566b9863e35a9d83acd49ad6824b5969738818301527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6606082015260808101949094523060a0808601919091528151808603909101815260c09094019052825192019190912060035550600580546001600160a01b03191633179055612308806101056000396000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c80636a627842116100f9578063ba9a7a5611610097578063d21220a711610071578063d21220a714610534578063d505accf1461053c578063dd62ed3e1461058d578063fff6cae9146105bb576101a9565b8063ba9a7a56146104fe578063bc25cf7714610506578063c45a01551461052c576101a9565b80637ecebe00116100d35780637ecebe001461046557806389afcb441461048b57806395d89b41146104ca578063a9059cbb146104d2576101a9565b80636a6278421461041157806370a08231146104375780637464fc3d1461045d576101a9565b806323b872dd116101665780633644e515116101405780633644e515146103cb578063485cc955146103d35780635909c0d5146104015780635a3d549314610409576101a9565b806323b872dd1461036f57806330adf81f146103a5578063313ce567146103ad576101a9565b8063022c0d9f146101ae57806306fdde031461023c5780630902f1ac146102b9578063095ea7b3146102f15780630dfe16811461033157806318160ddd14610355575b600080fd5b61023a600480360360808110156101c457600080fd5b8135916020810135916001600160a01b0360408301351691908101906080810160608201356401000000008111156101fb57600080fd5b82018360208201111561020d57600080fd5b8035906020019184600183028401116401000000008311171561022f57600080fd5b5090925090506105c3565b005b610244610afe565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561027e578181015183820152602001610266565b50505050905090810190601f1680156102ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102c1610b24565b604080516001600160701b03948516815292909316602083015263ffffffff168183015290519081900360600190f35b61031d6004803603604081101561030757600080fd5b506001600160a01b038135169060200135610b4e565b604080519115158252519081900360200190f35b610339610b65565b604080516001600160a01b039092168252519081900360200190f35b61035d610b74565b60408051918252519081900360200190f35b61031d6004803603606081101561038557600080fd5b506001600160a01b03813581169160208101359091169060400135610b7a565b61035d610c76565b6103b5610c9a565b6040805160ff9092168252519081900360200190f35b61035d610c9f565b61023a600480360360408110156103e957600080fd5b506001600160a01b0381358116916020013516610ca5565b61035d610d29565b61035d610d2f565b61035d6004803603602081101561042757600080fd5b50356001600160a01b0316610d35565b61035d6004803603602081101561044d57600080fd5b50356001600160a01b0316611035565b61035d611047565b61035d6004803603602081101561047b57600080fd5b50356001600160a01b031661104d565b6104b1600480360360208110156104a157600080fd5b50356001600160a01b031661105f565b6040805192835260208301919091528051918290030190f35b610244611405565b61031d600480360360408110156104e857600080fd5b506001600160a01b038135169060200135611427565b61035d611434565b61023a6004803603602081101561051c57600080fd5b50356001600160a01b031661143a565b6103396115a5565b6103396115b4565b61023a600480360360e081101561055257600080fd5b506001600160a01b03813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c001356115c3565b61035d600480360360408110156105a357600080fd5b506001600160a01b03813581169160200135166117c5565b61023a6117e2565b600c5460011461060e576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55841515806106215750600084115b61065c5760405162461bcd60e51b81526004018080602001828103825260258152602001806121f56025913960400191505060405180910390fd5b600080610667610b24565b5091509150816001600160701b03168710801561068c5750806001600160701b031686105b6106c75760405162461bcd60e51b815260040180806020018281038252602181526020018061223e6021913960400191505060405180910390fd5b60065460075460009182916001600160a01b039182169190811690891682148015906107055750806001600160a01b0316896001600160a01b031614155b61074e576040805162461bcd60e51b8152602060048201526015602482015274556e697377617056323a20494e56414c49445f544f60581b604482015290519081900360640190fd5b8a1561075f5761075f828a8d611944565b891561077057610770818a8c611944565b861561082b57886001600160a01b03166310d1e85c338d8d8c8c6040518663ffffffff1660e01b815260040180866001600160a01b03166001600160a01b03168152602001858152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509650505050505050600060405180830381600087803b15801561081257600080fd5b505af1158015610826573d6000803e3d6000fd5b505050505b604080516370a0823160e01b815230600482015290516001600160a01b038416916370a08231916024808301926020929190829003018186803b15801561087157600080fd5b505afa158015610885573d6000803e3d6000fd5b505050506040513d602081101561089b57600080fd5b5051604080516370a0823160e01b815230600482015290519195506001600160a01b038316916370a0823191602480820192602092909190829003018186803b1580156108e757600080fd5b505afa1580156108fb573d6000803e3d6000fd5b505050506040513d602081101561091157600080fd5b5051925060009150506001600160701b0385168a90038311610934576000610943565b89856001600160701b03160383035b9050600089856001600160701b031603831161096057600061096f565b89856001600160701b03160383035b905060008211806109805750600081115b6109bb5760405162461bcd60e51b815260040180806020018281038252602481526020018061221a6024913960400191505060405180910390fd5b60006109ef6109d184600363ffffffff611ade16565b6109e3876103e863ffffffff611ade16565b9063ffffffff611b4116565b90506000610a076109d184600363ffffffff611ade16565b9050610a38620f4240610a2c6001600160701b038b8116908b1663ffffffff611ade16565b9063ffffffff611ade16565b610a48838363ffffffff611ade16565b1015610a8a576040805162461bcd60e51b815260206004820152600c60248201526b556e697377617056323a204b60a01b604482015290519081900360640190fd5b5050610a9884848888611b91565b60408051838152602081018390528082018d9052606081018c905290516001600160a01b038b169133917fd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d8229181900360800190a350506001600c55505050505050505050565b6040518060400160405280600a8152602001692ab734b9bbb0b8102b1960b11b81525081565b6008546001600160701b0380821692600160701b830490911691600160e01b900463ffffffff1690565b6000610b5b338484611d56565b5060015b92915050565b6006546001600160a01b031681565b60005481565b6001600160a01b0383166000908152600260209081526040808320338452909152812054821115610bdc5760405162461bcd60e51b81526004018080602001828103825260258152602001806122af6025913960400191505060405180910390fd5b6001600160a01b038416600090815260026020908152604080832033845290915290205460001914610c61576001600160a01b0384166000908152600260209081526040808320338452909152902054610c3c908363ffffffff611b4116565b6001600160a01b03851660009081526002602090815260408083203384529091529020555b610c6c848484611db8565b5060019392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60035481565b6005546001600160a01b03163314610cfb576040805162461bcd60e51b81526020600482015260146024820152732ab734b9bbb0b82b191d102327a92124a22222a760611b604482015290519081900360640190fd5b600680546001600160a01b039384166001600160a01b03199182161790915560078054929093169116179055565b60095481565b600a5481565b6000600c54600114610d82576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c81905580610d92610b24565b50600654604080516370a0823160e01b815230600482015290519395509193506000926001600160a01b03909116916370a08231916024808301926020929190829003018186803b158015610de657600080fd5b505afa158015610dfa573d6000803e3d6000fd5b505050506040513d6020811015610e1057600080fd5b5051600754604080516370a0823160e01b815230600482015290519293506000926001600160a01b03909216916370a0823191602480820192602092909190829003018186803b158015610e6357600080fd5b505afa158015610e77573d6000803e3d6000fd5b505050506040513d6020811015610e8d57600080fd5b505190506000610eac836001600160701b03871663ffffffff611b4116565b90506000610ec9836001600160701b03871663ffffffff611b4116565b90506000610ed78787611e72565b60005490915080610f1457610f006103e86109e3610efb878763ffffffff611ade16565b611fd0565b9850610f0f60006103e8612022565b610f63565b610f606001600160701b038916610f31868463ffffffff611ade16565b81610f3857fe5b046001600160701b038916610f53868563ffffffff611ade16565b81610f5a57fe5b046120b8565b98505b60008911610fa25760405162461bcd60e51b81526004018080602001828103825260288152602001806122876028913960400191505060405180910390fd5b610fac8a8a612022565b610fb886868a8a611b91565b8115610fe857600854610fe4906001600160701b0380821691600160701b90041663ffffffff611ade16565b600b555b6040805185815260208101859052815133927f4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f928290030190a250506001600c5550949695505050505050565b60016020526000908152604090205481565b600b5481565b60046020526000908152604090205481565b600080600c546001146110ad576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c819055806110bd610b24565b50600654600754604080516370a0823160e01b815230600482015290519496509294506001600160a01b039182169391169160009184916370a08231916024808301926020929190829003018186803b15801561111957600080fd5b505afa15801561112d573d6000803e3d6000fd5b505050506040513d602081101561114357600080fd5b5051604080516370a0823160e01b815230600482015290519192506000916001600160a01b038516916370a08231916024808301926020929190829003018186803b15801561119157600080fd5b505afa1580156111a5573d6000803e3d6000fd5b505050506040513d60208110156111bb57600080fd5b5051306000908152600160205260408120549192506111da8888611e72565b600054909150806111f1848763ffffffff611ade16565b816111f857fe5b049a508061120c848663ffffffff611ade16565b8161121357fe5b04995060008b118015611226575060008a115b6112615760405162461bcd60e51b815260040180806020018281038252602881526020018061225f6028913960400191505060405180910390fd5b61126b30846120d0565b611276878d8d611944565b611281868d8c611944565b604080516370a0823160e01b815230600482015290516001600160a01b038916916370a08231916024808301926020929190829003018186803b1580156112c757600080fd5b505afa1580156112db573d6000803e3d6000fd5b505050506040513d60208110156112f157600080fd5b5051604080516370a0823160e01b815230600482015290519196506001600160a01b038816916370a0823191602480820192602092909190829003018186803b15801561133d57600080fd5b505afa158015611351573d6000803e3d6000fd5b505050506040513d602081101561136757600080fd5b5051935061137785858b8b611b91565b81156113a7576008546113a3906001600160701b0380821691600160701b90041663ffffffff611ade16565b600b555b604080518c8152602081018c905281516001600160a01b038f169233927fdccd412f0b1252819cb1fd330b93224ca42612892bb3f4f789976e6d81936496929081900390910190a35050505050505050506001600c81905550915091565b604051806040016040528060068152602001652aa72496ab1960d11b81525081565b6000610b5b338484611db8565b6103e881565b600c54600114611485576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55600654600754600854604080516370a0823160e01b815230600482015290516001600160a01b039485169490931692611534928592879261152f926001600160701b03169185916370a0823191602480820192602092909190829003018186803b1580156114f757600080fd5b505afa15801561150b573d6000803e3d6000fd5b505050506040513d602081101561152157600080fd5b50519063ffffffff611b4116565b611944565b600854604080516370a0823160e01b8152306004820152905161159b928492879261152f92600160701b90046001600160701b0316916001600160a01b038616916370a0823191602480820192602092909190829003018186803b1580156114f757600080fd5b50506001600c5550565b6005546001600160a01b031681565b6007546001600160a01b031681565b4284101561160d576040805162461bcd60e51b8152602060048201526012602482015271155b9a5cddd85c158c8e881156141254915160721b604482015290519081900360640190fd5b6003546001600160a01b0380891660008181526004602090815260408083208054600180820190925582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98186015280840196909652958d166060860152608085018c905260a085019590955260c08085018b90528151808603909101815260e08501825280519083012061190160f01b6101008601526101028501969096526101228085019690965280518085039096018652610142840180825286519683019690962095839052610162840180825286905260ff89166101828501526101a284018890526101c28401879052519193926101e280820193601f1981019281900390910190855afa158015611728573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b0381161580159061175e5750886001600160a01b0316816001600160a01b0316145b6117af576040805162461bcd60e51b815260206004820152601c60248201527f556e697377617056323a20494e56414c49445f5349474e415455524500000000604482015290519081900360640190fd5b6117ba898989611d56565b505050505050505050565b600260209081526000928352604080842090915290825290205481565b600c5460011461182d576040805162461bcd60e51b8152602060048201526011602482015270155b9a5cddd85c158c8e881313d0d2d151607a1b604482015290519081900360640190fd5b6000600c55600654604080516370a0823160e01b8152306004820152905161193d926001600160a01b0316916370a08231916024808301926020929190829003018186803b15801561187e57600080fd5b505afa158015611892573d6000803e3d6000fd5b505050506040513d60208110156118a857600080fd5b5051600754604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b1580156118f557600080fd5b505afa158015611909573d6000803e3d6000fd5b505050506040513d602081101561191f57600080fd5b50516008546001600160701b0380821691600160701b900416611b91565b6001600c55565b604080518082018252601981527f7472616e7366657228616464726573732c75696e74323536290000000000000060209182015281516001600160a01b0385811660248301526044808301869052845180840390910181526064909201845291810180516001600160e01b031663a9059cbb60e01b1781529251815160009460609489169392918291908083835b602083106119f15780518252601f1990920191602091820191016119d2565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114611a53576040519150601f19603f3d011682016040523d82523d6000602084013e611a58565b606091505b5091509150818015611a86575080511580611a865750808060200190516020811015611a8357600080fd5b50515b611ad7576040805162461bcd60e51b815260206004820152601a60248201527f556e697377617056323a205452414e534645525f4641494c4544000000000000604482015290519081900360640190fd5b5050505050565b6000811580611af957505080820282828281611af657fe5b04145b610b5f576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6d756c2d6f766572666c6f7760601b604482015290519081900360640190fd5b80820382811115610b5f576040805162461bcd60e51b815260206004820152601560248201527464732d6d6174682d7375622d756e646572666c6f7760581b604482015290519081900360640190fd5b6001600160701b038411801590611baf57506001600160701b038311155b611bf6576040805162461bcd60e51b8152602060048201526013602482015272556e697377617056323a204f564552464c4f5760681b604482015290519081900360640190fd5b60085463ffffffff42811691600160e01b90048116820390811615801590611c2657506001600160701b03841615155b8015611c3a57506001600160701b03831615155b15611cab578063ffffffff16611c6885611c538661216e565b6001600160e01b03169063ffffffff61218016565b600980546001600160e01b03929092169290920201905563ffffffff8116611c9384611c538761216e565b600a80546001600160e01b0392909216929092020190555b600880546dffffffffffffffffffffffffffff19166001600160701b03888116919091176dffffffffffffffffffffffffffff60701b1916600160701b8883168102919091176001600160e01b0316600160e01b63ffffffff871602179283905560408051848416815291909304909116602082015281517f1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1929181900390910190a1505050505050565b6001600160a01b03808416600081815260026020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6001600160a01b038316600090815260016020526040902054611de1908263ffffffff611b4116565b6001600160a01b038085166000908152600160205260408082209390935590841681522054611e16908263ffffffff6121a516565b6001600160a01b0380841660008181526001602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b600080600560009054906101000a90046001600160a01b03166001600160a01b031663017e7e586040518163ffffffff1660e01b815260040160206040518083038186803b158015611ec357600080fd5b505afa158015611ed7573d6000803e3d6000fd5b505050506040513d6020811015611eed57600080fd5b5051600b546001600160a01b038216158015945091925090611fbc578015611fb7576000611f30610efb6001600160701b0388811690881663ffffffff611ade16565b90506000611f3d83611fd0565b905080821115611fb4576000611f6b611f5c848463ffffffff611b4116565b6000549063ffffffff611ade16565b90506000611f9083611f8486600563ffffffff611ade16565b9063ffffffff6121a516565b90506000818381611f9d57fe5b0490508015611fb057611fb08782612022565b5050505b50505b611fc8565b8015611fc8576000600b555b505092915050565b60006003821115612013575080600160028204015b8181101561200d57809150600281828581611ffc57fe5b04018161200557fe5b049050611fe5565b5061201d565b811561201d575060015b919050565b600054612035908263ffffffff6121a516565b60009081556001600160a01b038316815260016020526040902054612060908263ffffffff6121a516565b6001600160a01b03831660008181526001602090815260408083209490945583518581529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35050565b60008183106120c757816120c9565b825b9392505050565b6001600160a01b0382166000908152600160205260409020546120f9908263ffffffff611b4116565b6001600160a01b03831660009081526001602052604081209190915554612126908263ffffffff611b4116565b60009081556040805183815290516001600160a01b038516917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef919081900360200190a35050565b6001600160701b0316600160701b0290565b60006001600160701b0382166001600160e01b0384168161219d57fe5b049392505050565b80820182811015610b5f576040805162461bcd60e51b815260206004820152601460248201527364732d6d6174682d6164642d6f766572666c6f7760601b604482015290519081900360640190fdfe556e697377617056323a20494e53554646494349454e545f4f55545055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e54556e697377617056323a20494e53554646494349454e545f4c4951554944495459556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4255524e4544556e697377617056323a20494e53554646494349454e545f4c49515549444954595f4d494e544544556e6973776170563245524332303a206e6f7420656e6f75676820616c6c6f77616e63652ea265627a7a72315820bf165e44001b5284076af3e56685540f3d6256d9ddeae5a8f22ed60b1397a80064736f6c63430005100032454950373132446f6d61696e28737472696e67206e616d652c737472696e672076657273696f6e2c75696e7432353620636861696e49642c6164647265737320766572696679696e67436f6e747261637429
*/
pair code hash(python):
0xd0b942f04f5999da18c6933b57c7a558fc3b364ba0e233f5828591ee21b66669
pair code hash(evm):
0xd0b942f04f5999da18c6933b57c7a558fc3b364ba0e233f5828591ee21b66669
pair address of WBTC-USDT(calculated from python):
0x841f5e40d7573fd7fbe5a8a002a10759ad5e3bf3
pair address of WBTC-USDT(from EVM create2):
0x841F5E40d7573FD7FbE5A8A002a10759aD5E3BF3
复制ok, 第一部分暂时分享到这里, 接下来我们继续一起学习吧.作者 mail:star4evar@gmail.com参考资料[1]tony.ho: https://learnblockchain.cn/people/8619[2]Uniswap: https://learnblockchain.cn/article/274本文参与 腾讯云自媒体分享计划,分享自微信公众号。原始发表:2022-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除https网络安全python本文分享自 深入浅出区块链技术 微信公众号,前往查看如有侵权,请联系 cloudcommunity@tencent.com 删除。本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!https网络安全python评论登录后参与评论0 条评论热度最新登录 后参与评论推荐阅读LV.关注文章0获赞0目录概览Pair 的创建 和 create2 的地址计算:参考资料领券社区专栏文章阅读清单互动问答技术沙龙技术视频团队主页腾讯云TI平台活动自媒体分享计划邀请作者入驻自荐上首页技术竞赛资源技术周刊社区标签开发者手册开发者实验室关于社区规范免责声明联系我们友情链接腾讯云开发者扫码关注腾讯云开发者领取腾讯云代金券热门产品域名注册云服务器区块链服务消息队列网络加速云数据库域名解析云存储视频直播热门推荐人脸识别腾讯会议企业云CDN加速视频通话图像分析MySQL 数据库SSL 证书语音识别更多推荐数据安全负载均衡短信文字识别云点播商标注册小程序开发网站监控数据迁移Copyright © 2013 - 2024 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有 深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档Copyright © 2013 - 2024 Tencent Cloud.All Rights Reserved. 腾讯云 版权所有登录 后参与评论100
工厂合约 | Uniswap V3 Book 中文版
| Uniswap V3 Book 中文版Uniswap V3 Book 中文版Milestone 0. 简介交易市场简介恒定函数做市商(CFMM)Uniswap V3开发环境Milestone 1. 第一笔交易简介计算流动性提供流动性第一笔交易管理合约部署合约用户界面Milestone 2. 第二笔交易简介输出金额计算Solidity中的数学运算Tick Bitmap Index通用mint通用swap报价合约用户界面Milestone 3. 跨tick交易简介不同价格区间跨tick交易滑点保护流动性计算关于定点数的拓展闪电贷用户界面Milestone 4. 多池子交易简介工厂合约交易路径多池子交易用户界面Tick 舍入Milestone 5. 费率和价格预言机简介交易费率闪电贷费率协议费率价格预言机用户界面Milestone 6: NFT positions简介ERC721 概述NFT 管理员合约NFT 渲染器补充资料中英名词对照
工厂合约
工厂合约CREATE 和 CREATE2 OpcodesTick 间隔工厂合约实现池子初始化PoolAddress 库简化 Manager 和 Quoter 的接口工厂合约
#Uniswap 由多个离散的池子合约构成,每个池子负责一对 token 的交易。这看起来会有些问题,因为当我们想要在两个没有池子的 token 之间进行交易时——由于没有赤字,我们无法进行交易。然而,我们仍然可以进行中间交易:第一笔交易把一种 token 转换成另一种有交易对的 token,然后再把这种 token 转换成目标 token。这个路径可以更长并且有更多种的中间 token。然而,手动进行这一个操作会非常繁琐,幸运的是,我们可以在我们的智能合约中实现这个功能,让其更简便。*工厂(Factory)*合约是一个拥有以下功能的合约:它作为池子合约的中心化注册点。在工厂中,你可以找到所有已部署的池子,对应的 token 和地址。它简化了池子合约的部署流程。EVM允许在智能合约中部署智能合约——工厂合约使用这个性质来让池子合约的部署变得十分简单。它让池子合约的地址可预测,并且能够在注册池子之前就计算出这个地址。这让池子更容易被发现。让我们来搭建工厂合约吧!但在此之前,我们还需要学一些新东西。CREATE 和 CREATE2 Opcodes
#EVM 有两种部署合约的方式:通过 CREATE 或者 CREATE2 opcode。两者之间的唯一区别试新地址如何产生:CREATE 使用部署者账户的 nonce 来产生新的合约地址(伪代码如下):KECCAK256(deployer.address, deployer.nonce)
nonce 是一个账户特定的交易计数器。在产生合约地址过程中使用 nonce 会使得在其他合约或者链下app中计算合约地址变得非常困难,主要是因为想要找到对应的 nonce,我们需要扫描账户的交易历史。CREATE2 使用一个特殊的*盐值(salt)*来产生合约地址。这是一个由开发者选择的任意序列,能够使得地址产生更加确定性(并降低碰撞概率):KECCAK256(deployer.address, salt, contractCodeHash)
我们需要知道两者的区别,因为工厂在部署池子合约时使用的是 CREATE2,所以池子可以获得唯一并且确定性的、能够由其他合约和链下 app 计算出来的地址。在盐值方面,工厂使用这些池子的参数计算哈希:keccak256(abi.encodePacked(token0, token1, tickSpacing))
token0 和 token1 是池子里两种 token 的地址,而 tickSpacing 是我们下面将要讨论的内容。Tick 间隔
#回顾一下我们在 swap 函数中的循环:while (
state.amountSpecifiedRemaining > 0 &&
state.sqrtPriceX96 != sqrtPriceLimitX96
) {
...
(step.nextTick, ) = tickBitmap.nextInitializedTickWithinOneWord(...);
(state.sqrtPriceX96, step.amountIn, step.amountOut) = SwapMath.computeSwapStep(...);
...
}
这个循环通过在一个方向上遍历来寻找拥有流动性的已初始化的 tick。然而,这个循环时非常昂贵的:如果一个 tick 离得很远,代码将会经过两个 tick 之间的所有 tick,十分消耗 gas。为了让循环更节约 gas,Uniswap 的池子有一个叫做 tickSpacing 的参数设定;正如其名字所示,代表两个 tick 之间的距离——距离越大,越节省 gas。然而,tick 间隔越大,精度越低。价格波动性低的交易对(例如两种稳定币的池子)需要更高的精度,因为在这样的池子中价格移动很小;价格波动性中等和较高的交易对可以有更低的精度,因为在这样的交易对中价格移动会很大。为了处理这样的多样性,Uniswap 允许在交易对创建时设定一个 tick 间隔。Uniswap 允许部署者在下列选项中选择:10,60,200,而简单起见我们的实现中只考虑10和60。在实际中,tick index只能够是 tickSpacing 的整数倍:如果 tickSpacing 是 10,仅有 10 的倍数才是有效的 tick index(10,20,5000,5010等,但是8,12,5001这些不可以)。然而,需要注意的是,这个限制对于现价不起作用——现价所在的 tick 仍然可以是任意的 tick,因为我们希望价格尽可能精确。tickSpacing 参数仅仅限制价格区间对应 tick。因此,一个池子可以由以下参数唯一确定:token0,token1,tickSpacing;正如你想的那样,可以有 token 相同但是 tickSpacing 不同的池子存在。工厂合约使用这组参数来作为池子的唯一定位,并且把他们作为盐值来产生池子合约地址。从现在开始,我们假定所有池子的 tick 间隔为60,而在稳定币交易对中使用10。工厂合约实现
#在工厂合约的构造函数中,我们需要初始化支持的 tick 间隔:// src/UniswapV3Factory.sol
contract UniswapV3Factory is IUniswapV3PoolDeployer {
mapping(uint24 => bool) public tickSpacings;
constructor() {
tickSpacings[10] = true;
tickSpacings[60] = true;
}
...
我们的确可以让它们是常数,但在后面的 milestone 中我们会希望它是一个映射(不同的 tick 间隔会有不同的交易费)。工厂合约中的唯一函数是 createPool。这个函数首先检查创建池子所需要的所有必要条件:// src/UniswapV3Factory.sol
contract UniswapV3Factory is IUniswapV3PoolDeployer {
PoolParameters public parameters;
mapping(address => mapping(address => mapping(uint24 => address)))
public pools;
...
function createPool(
address tokenX,
address tokenY,
uint24 tickSpacing
) public returns (address pool) {
if (tokenX == tokenY) revert TokensMustBeDifferent();
if (!tickSpacings[tickSpacing]) revert UnsupportedTickSpacing();
(tokenX, tokenY) = tokenX < tokenY
? (tokenX, tokenY)
: (tokenY, tokenX);
if (tokenX == address(0)) revert TokenXCannotBeZero();
if (pools[tokenX][tokenY][tickSpacing] != address(0))
revert PoolAlreadyExists();
...
注意到这是我们第一次在代码中看到 token 的排序:(tokenX, tokenY) = tokenX < tokenY
? (tokenX, tokenY)
: (tokenY, tokenX);
从现在开始,我们将永远认为池子的 token 地址是有序的,也即 token0 地址小于 token1。我们需要强制这一点来保证盐值和合约地址的计算永远一致。这个改变也会影响我们在测试和部署脚本中部署 token 的方式:我们需要确保 WETH 总是 token0,来使得 Solidity 中的价格计算更简单(否则,我们的价格就会是分数,比如1/5000这样)。如果 WETH 在你的测试中不是 token0,改变一下 token 部署的顺序。之后,我们准备部署合约需要的参数:parameters = PoolParameters({
factory: address(this),
token0: tokenX,
token1: tokenY,
tickSpacing: tickSpacing
});
pool = address(
new UniswapV3Pool{
salt: keccak256(abi.encodePacked(tokenX, tokenY, tickSpacing))
}()
);
delete parameters;
这段代码看起来很奇怪,因为 parameters 并没有用到。Uniswap 这里使用了控制反转(Inversion of Control)来在池子创建的过程中传递参数。译者注:控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度(来源 wiki)让我们看一下更新后的池子合约构造函数:// src/UniswapV3Pool.sol
contract UniswapV3Pool is IUniswapV3Pool {
...
constructor() {
(factory, token0, token1, tickSpacing) = IUniswapV3PoolDeployer(
msg.sender
).parameters();
}
..
}
池子合约需要部署者实现了 IUniswapV3PoolDeployer 接口(仅仅定义了 parameters() 这样一个 getter)并且在构造函数中调用它来获取参数。控制流长下面这样:Factory:定义 parameters 状态变量(实现 IUniswapV3PoolDeployer)并在部署池子之前设置其值。Factory:部署池子。Pool:在构造函数中,调用部署者的 parameters() 函数,希望从返回值中获取参数。Factory 调用 delete parameters; 来清理 parameter 状态变量占用的 slot 来减少 gas 开销。这仅仅是一个临时的状态变量,只在调用 createPool() 时有值。在池子创建后,我们把它存储在 pools 映射中(这样就能够被找到),并发出一个事件: pools[tokenX][tokenY][tickSpacing] = pool;
pools[tokenY][tokenX][tickSpacing] = pool;
emit PoolCreated(tokenX, tokenY, tickSpacing, pool);
}
池子初始化
#正如你在上面代码中看到的,我们不再在池子的构造函数中设置 sqrtPriceX96 和 tick——它现在在另一个函数 initialize 中完成,这个函数在池子部署后调用:// src/UniswapV3Pool.sol
function initialize(uint160 sqrtPriceX96) public {
if (slot0.sqrtPriceX96 != 0) revert AlreadyInitialized();
int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick});
}
这是我们现在部署池子的方式:UniswapV3Factory factory = new UniswapV3Factory();
UniswapV3Pool pool = UniswapV3Pool(factory.createPool(token0, token1, tickSpacing));
pool.initialize(sqrtP(currentPrice));
PoolAddress 库
#现在我们来实现一个库帮助我们计算池子合约的地址。这个库只有一个函数,computeAddress:// src/lib/PoolAddress.sol
library PoolAddress {
function computeAddress(
address factory,
address token0,
address token1,
uint24 tickSpacing
) internal pure returns (address pool) {
require(token0 < token1);
...
这个函数需要知道池子的参数(用来构成盐值)和工厂合约的地址。它需要 token 事先被排序,正如上文所述。这个函数的核心部分:pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(
abi.encodePacked(token0, token1, tickSpacing)
),
keccak256(type(UniswapV3Pool).creationCode)
)
)
)
)
);
这正是 CREATE2 计算新合约地址的底层实现方式。我们来拆解一下:首先,我们计算盐值(abi.encodePacked(token0, token1, tickSpacing))并求哈希;接下来,我们获取池子合约的代码(type(UniswapV3Pool).creationCode)并求哈希;然后,我们构建这样一个字节序列:0xff,工厂合约地址,哈希后的盐值,哈希后的池子合约代码最后求这个序列的哈希并转换成地址。这些步骤实现了在 EIP-1014 中定义的地址产生方式,这也就是那个增加了 CREATE2 操作码的 EIP。我们来进一步看一下组成这个字节序列的值:0xff 是在 EIP 中定义的,为了区分由 CREATE 和 CREATE2 创建的合约地址。factory 是调用者的地址,也即我们这里的工厂合约盐值在之前提到过——用来唯一定位池子合约代码的哈希用来防止碰撞——不同的合约可以有相同的盐值,但是它们的代码哈希会不相同。根据这样的模式,一个合约的地址由一系列唯一标识这个合约得值哈希得到,包含它的部署者、代码、和唯一参数。我们可以在任何地方使用这个函数来求出池子的地址,而不需要进行任何外部调用或者请求工厂合约。简化 Manager 和 Quoter 的接口
#在管理员合约和报价合约中,我们不再需要向用户请求池子地址!这使得与合约的交互更简单,因为用户不需要知道池子的地址,他们只需要知道 token。然而,用户仍然需要指定 tick 间隔,因为求池子的盐值需要它。而且,我们也不再需要向用户请求 zeroForOne 参数,因为有了 token 的排序我们能够直接求出这个参数了。zeroForOne 在 “from token” 小于 “to token” 的时候为 true,因为池子的 token0 总是小于 token1。类似地,zeroForOne 在 “from token” 大于 “to token” 的时候为 false。地址是哈希,哈希也是数字,所以我们能够用“大于”或者“小于”来比较地址。工厂合约CREATE 和 CREATE2 OpcodesTick 间隔工厂合约实现池子初始化PoolAddress 库简化 Manager 和 Quoter
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)_部署 uniswap v2-CSDN博客
>完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)_部署 uniswap v2-CSDN博客
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
最新推荐文章于 2023-05-25 20:38:21 发布
zgf1991
最新推荐文章于 2023-05-25 20:38:21 发布
阅读量1.9w
收藏
97
点赞数
17
分类专栏:
以太坊
文章标签:
uniswap
以太坊
DEFI
uniswap交易所
去中心化交易所
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zgf1991/article/details/109127260
版权
以太坊
专栏收录该内容
7 篇文章
6 订阅
订阅专栏
文章目录
前提条件部署合约部署工厂和WETH合约部署路由合约(重要环节!!)步骤1 获取字节码步骤2 获得initCode步骤3 替换路由中的initCode
当前部署结果部署前端部署前端到其他平台(bsc/heco)需要替换的地方1、interface-2.6.0\node_modules\@uniswap\sdk\dist\constants.d.ts (chainID,工厂)2、interface-2.6.0\node_modules\@uniswap\sdk\dist\sdk.esm.js (chainID,工厂,weth)3、interface-2.6.0\node_modules\@uniswap\default-token-list\build\uniswap-default.tokenlist.json(weth)4、interface-2.6.0\node_modules\@uniswap\sdk\dist\entities\token.d.ts(weth)5、interface-2.6.0\src\connectors\index.ts(网页支持的chainID)6、interface-2.6.0\src\constants\index.ts(路由,weth)7、interface-2.6.0\src\constants\multicall\index.ts(multicall)8、interface-2.6.0\src\constants\v1\index.ts(v1 factory避免报错)9、interface-2.6.0\src\state\lists\hooks.ts(增加chainID配置)10、interface-2.6.0\src\utils\index.ts(浏览器跳转查看hash)11、interface-2.6.0\src\components\Header\index.tsx(显示的网络名称)
其他补充(懂solidity的可以看看)添加流动性交换方法工具in/out计算公式推导
参考链接 崔棉大师的教程
手把手教你部署自己的uniswap交易所
之前部署是跟着崔棉大师的教程走的,但是部署完了,没法实际使用,添加流动性还是交易会报错 这里主要是做补充;
前提条件
自己有账号,且申请测试以太坊 (ropsten直接小狐狸 buy 打开链接领,rinkeby需要推特发链接再去领取)会使用 remix 部署合约部署前端需会使用 npm / yarn
部署合约
合约源代码 此处只部署routerV2
工厂合约WETH路由02
注意事项
部署工厂合约和路由合约时,EVM VERSION 选择 istanbul, COMPILER CONFIGURATION 中勾选 Enable optimization ; WETH部署时 EVM VERSION 选择 default; 工厂合约和WETH合约可以直接部署,路由合约需要修改一个Code
部署工厂和WETH合约
该步骤略, 这两个直接部署即可 weth代码中添加以下代码,便于直接获取任意数量WETH,方便测试大额交易
//直接获取WETH
function mint(uint _value)public payable{
balanceOf[msg.sender] += _value;
Deposit(msg.sender, _value);
}
部署路由合约(重要环节!!)
initCode 重要环节,我刚开始部署的时候就是这一步不清楚导致的部署的合约,无法使用, 至于为啥会不一样,不太清楚!
步骤1 获取字节码
编译工厂合约,获取pair的字节码, 看下图
获得类似这样的
得到以下结构的内容, 只需要object字段的内容; 复制
{
"linkReferences": {},
"object": "取这里的内容",
"opcodes": "-",
"sourceMap": "-"
}
步骤2 获得initCode
打开网址 http://emn178.github.io/online-tools/keccak_256.html
将刚才得到的object字段内容粘贴,选择input type HEX 如下图
步骤3 替换路由中的initCode
将 96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f 替换成 de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17 (步骤2中获取的)
// 路由中该代码
// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17' //init code hash
//hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' //init code hash
))));
}
编译部署即可
当前部署结果
添加流动性 ropsten https://ropsten.etherscan.io/tx/0x90a860e95f2796b08985b02a1163eccb58efefd053e0a80ebf75cca8f7f5b8fa
rinkeby https://rinkeby.etherscan.io/tx/0x4c82a23ec995bf404a98e39b490c5e7893945c4341495c7fe6de43b90e646aeb
新账号,所以rinkeby和ropsten两个测试网都是部署的以下地址
工厂
0x2CD020750216583CCF657a0949F0843ec1f73EFE
WETH
0x57E25a96A6dBA2cA02e9C96d08f672574c6E6B13
路由
0x9A36D38C6De905f969C172a85dD362E3Bc36B936
initCode
de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17
测试Token
0xa5BA457F1DfdCC3E65515E69E545292D203b0E76
另外吐槽下… ropsten 获取测试币最简便,但是打包忒慢了 rinkeby 打包很快,不过要发推后再领, 有梯子的建议使用这个
部署前端
前端代码 可以clone最好, 太慢的话就直接下载zip解压 1、自行下载好源码 2、安装好yarn 3、修改代码
修改文件: 项目目录/uniswap-interface/src/constants/index.ts 第 6 行
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' //修改成你的路由合约地址
修改2 node_modules下面有个@uniswap/sdk/dist/constants.d.ts和sdk.esm.js 这两个文件里面修改factory地址和initCode;改完添加流动性,或者查找pair就没问题了;
如果想修改默认的weth;路径@uniswap/default-token-list/build 新版本的路径可能改了, 全局搜索下主网的weth,找到对应的替换就行
注: 前端代码可以打开IDE, 然后全局搜索替换成自己部署的信息,之后编译代码就行了!!!
//uniswap官方部署的信息
工厂
0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
WETH
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
路由
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
initCode
96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f
将以上4个信息都替换成自己部署的合约地址
工厂
0x2CD020750216583CCF657a0949F0843ec1f73EFE
WETH
0x57E25a96A6dBA2cA02e9C96d08f672574c6E6B13
路由
0x9A36D38C6De905f969C172a85dD362E3Bc36B936
initCode
de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17
如要替换weth,需要注意环境替换
{
mainnet:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
ropsten:'0xc778417E063141139Fce010982780140Aa0cD5Ab', ( "chainId": 3,)
rinkeby:'0xc778417E063141139Fce010982780140Aa0cD5Ab', ("chainId": 4)
goerli:'0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
kovan:'0xd0A1E359811322d97991E03f863a0C30C2cF029C'
}
替换完后,编译
$ cd uniswap-interface
$ yarn
$ yarn start
//执行 yarn start 后跳出个网页 http://localhost:3000/#/swap
部署前端到其他平台(bsc/heco)需要替换的地方
部分参考崔棉大师最新的部署视频
当前以uniswap-interface-2.6.0为例
以下替换没有整理顺序,以我这边测试所需要的替换全部列出 当前是部署到BSC测试网例子
1、interface-2.6.0\node_modules@uniswap\sdk\dist\constants.d.ts (chainID,工厂)
增加chainID,替换factory,initcode
//line3,增加BSC 97
export declare enum ChainId {
MAINNET = 1,
ROPSTEN = 3,
RINKEBY = 4,
GÖRLI = 5,
KOVAN = 42,
BSC = 97
}
//line20,替换工厂和initcode
export declare const FACTORY_ADDRESS = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f";
export declare const INIT_CODE_HASH = "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f";
2、interface-2.6.0\node_modules@uniswap\sdk\dist\sdk.esm.js (chainID,工厂,weth)
增加chainID,替换factory,initcode 增加weth
同目录下sdk.cjs.development.js,如有需要,也一样的方式替换
//line18,增加BSC
(function (ChainId) {
ChainId[ChainId["MAINNET"] = 1] = "MAINNET";
ChainId[ChainId["ROPSTEN"] = 3] = "ROPSTEN";
ChainId[ChainId["RINKEBY"] = 4] = "RINKEBY";
ChainId[ChainId["G\xD6RLI"] = 5] = "G\xD6RLI";
ChainId[ChainId["KOVAN"] = 42] = "KOVAN";
ChainId[ChainId["BSC"] = 97] = "BSC";
})(ChainId || (ChainId = {}));
//替换factory,initcode
var FACTORY_ADDRESS = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f';
var INIT_CODE_HASH = '0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f';
//line442,增加对应的weth
var WETH = (_WETH = {}, _WETH[ChainId.MAINNET] = /*#__PURE__*/new Token(ChainId.MAINNET, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'Wrapped Ether'), _WETH[ChainId.ROPSTEN] = /*#__PURE__*/new Token(ChainId.ROPSTEN, '0xc778417E063141139Fce010982780140Aa0cD5Ab', 18, 'WETH', 'Wrapped Ether'), _WETH[ChainId.RINKEBY] = /*#__PURE__*/new Token(ChainId.RINKEBY, '0xc778417E063141139Fce010982780140Aa0cD5Ab', 18, 'WETH', 'Wrapped Ether'), _WETH[ChainId.GÖRLI] = /*#__PURE__*/new Token(ChainId.GÖRLI, '0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6', 18, 'WETH', 'Wrapped Ether'), _WETH[ChainId.KOVAN] = /*#__PURE__*/new Token(ChainId.KOVAN, '0xd0A1E359811322d97991E03f863a0C30C2cF029C', 18, 'WETH', 'Wrapped Ether'), _WETH[ChainId.BSC] = /*#__PURE__*/new Token(ChainId.BSC, '0x替换成自己的WETH地址', 18, 'WETH', 'Wrapped Ether'), _WETH);
3、interface-2.6.0\node_modules@uniswap\default-token-list\build\uniswap-default.tokenlist.json(weth)
WETH
//文件最下面按照格式增加一个weth
{
"name": "Wrapped Ether",
"address": "0x替换自己部署的weth",
"symbol": "WETH",
"decimals": 18,
"chainId": 97,
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xd0A1E359811322d97991E03f863a0C30C2cF029C/logo.png"
}
4、interface-2.6.0\node_modules@uniswap\sdk\dist\entities\token.d.ts(weth)
WETH
export declare const WETH: {
1: Token;
3: Token;
4: Token;
5: Token;
42: Token;
97: Token;
};
5、interface-2.6.0\src\connectors\index.ts(网页支持的chainID)
增加支持的chainID
//line29,支持的网络 增加97
export const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42, 97]
})
6、interface-2.6.0\src\constants\index.ts(路由,weth)
替换路由,增加weth
//line6,替换路由地址
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
//line20,增加BSC
const WETH_ONLY: ChainTokenList = {
[ChainId.MAINNET]: [WETH[ChainId.MAINNET]],
[ChainId.ROPSTEN]: [WETH[ChainId.ROPSTEN]],
[ChainId.RINKEBY]: [WETH[ChainId.RINKEBY]],
[ChainId.GÖRLI]: [WETH[ChainId.GÖRLI]],
[ChainId.KOVAN]: [WETH[ChainId.KOVAN]],
[ChainId.BSC]: [WETH[ChainId.BSC]]
}
7、interface-2.6.0\src\constants\multicall\index.ts(multicall)
替换multicall 合约地址
合约代码 https://cn.etherscan.com/address/0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441#code
直接复制下,部署一个即可
const MULTICALL_NETWORKS: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xeefBa1e63905eF1D7ACbA5a8513c70307C1cE441',
[ChainId.ROPSTEN]: '0x53C43764255c17BD724F74c4eF150724AC50a3ed',
[ChainId.KOVAN]: '0x2cc8688C5f75E365aaEEb4ea8D6a480405A48D2A',
[ChainId.RINKEBY]: '0x42Ad527de7d4e9d9d011aC45B31D8551f8Fe9821',
[ChainId.GÖRLI]: '0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e',
[ChainId.BSC]: '0x替换成自己部署的地址'
}
8、interface-2.6.0\src\constants\v1\index.ts(v1 factory避免报错)
v1的factory,增加一个空的就好了
const V1_FACTORY_ADDRESSES: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
[ChainId.ROPSTEN]: '0x9c83dCE8CA20E9aAF9D3efc003b2ea62aBC08351',
[ChainId.RINKEBY]: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
[ChainId.GÖRLI]: '0x6Ce570d02D73d4c384b46135E87f8C592A8c86dA',
[ChainId.KOVAN]: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30',
[ChainId.BSC]: ''
}
9、interface-2.6.0\src\state\lists\hooks.ts(增加chainID配置)
//line33,增加一个
/**
* An empty result, useful as a default.
*/
const EMPTY_LIST: TokenAddressMap = {
[ChainId.KOVAN]: {},
[ChainId.RINKEBY]: {},
[ChainId.ROPSTEN]: {},
[ChainId.GÖRLI]: {},
[ChainId.MAINNET]: {},
[ChainId.BSC]: {}
}
10、interface-2.6.0\src\utils\index.ts(浏览器跳转查看hash)
该修改主要用于交易后的提示hash,可以直接点击到浏览器查看 注意host不要斜杠 / 结尾
//line20-30, 直接用下面的替换
const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
1: 'https://etherscan.io',
3: 'https://ropsten.etherscan.io',
4: 'https://rinkeby.etherscan.io',
5: 'https://goerli.etherscan.io',
42: 'https://kovan.etherscan.io',
97: 'https://testnet.bscscan.com'
}
export function getEtherscanLink(chainId: ChainId, data: string, type: 'transaction' | 'token' | 'address'): string {
const prefix = `${ETHERSCAN_PREFIXES[chainId] || ETHERSCAN_PREFIXES[1]}`
11、interface-2.6.0\src\components\Header\index.tsx(显示的网络名称)
网页右上角显示网络名称
//line129, ChainId.BSC是在sdk中添加的, 对应的值Bsc只是一个展示
const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
[ChainId.MAINNET]: null,
[ChainId.RINKEBY]: 'Rinkeby',
[ChainId.ROPSTEN]: 'Ropsten',
[ChainId.GÖRLI]: 'Görli',
[ChainId.KOVAN]: 'Kovan',
[ChainId.BSC]: 'Bsc'
}
其他补充(懂solidity的可以看看)
如果会solidity,且看uniswap源码的,可以往下看看
崔棉大师有个Uniswap源码中文注解的文档,有需要的可以去购买
添加流动性
添加流动性需要输入两个token, 带ETH的方法,router会帮你转成WETH,最终实际就是该方法
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)
交换方法
这里主要说明交易的方法 这里主要归纳为3个方法(带ETH的方法,router会帮你转成WETH,最终都是两个ERC20交换)
说明:
交换方法中,不存在买卖的说法,只有in/out; 我下面的方法注释写买/卖是为了便于理解交换方法中所有参数in/out 都是相对于路由自己
//方法1 需要获取精确的输出,输入不确定金额(也可以理解买,如购买100个UNI TOKEN,需要未知weth)
function swapTokensForExactTokens(
uint amountOut,//期望输出金额
uint amountInMax,//最大输入 (如果到你打包的交易时,如果实际需要输入的金额大于该金额,交易失败!)
address[] calldata path,
address to,
uint deadline
)
//方法2 通过输入金额,输出不确定金额 (也可以理解为卖, 如卖掉100个UNI TOKEN,可以获得未知WETH)
function swapExactTokensForTokens(
uint amountIn,//实际输入金额
uint amountOutMin,//最小输出 (如果到打包你的交易时,实际输出小于该金额,交易失败!)
address[] calldata path,
address to,
uint deadline
)
//方法3 通过输入金额,输出不确定金额
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
此处说明上面方法2/3 的区别
方法3是以交易对实际获取到了多少代币,去调用交易对的交换 方法2是以用户调用转账输入的金额(该金额可能是错的,比如没有实际输入,或者扣了手续费),去调用交易对交换
比如黑币,在你转账的时候,扣除你20% 30%等 如果使用方法2 是无法成功 会提示UniswapV2: K 如果使用方法3,把amountOutMin填0,那么这个交易一定可以成功,哪怕交易对只给返回0.00000000001个以太坊
工具
// returns sorted token addresses, used to handle return values from pairs sorted in this order
//两个地址排序
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
// calculates the CREATE2 address for a pair without making any external calls
// 计算交易对地址, 注意这个init code hash... 这是个坑
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'de683b3097cb455dd2d3ea50f1f95386fdeca75180cc01bb6b12207c44272e17' // init code hash
))));
}
// fetches and sorts the reserves for a pair
//获取当前储备量,返回值会根据你输入的token排序
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
//添加流动性时,通过tokenA输入额,计算tokenB需要输入多少
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
//通过in计算out (后面详细说明)
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
//通过out 计算in (后面详细说明)
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
in/out计算公式推导
注:
此处主要说getAmountOut 和 getAmountIn 两个方法 上面工具中的两个方法内部算法是简化过的。
/**
*
*
* 推导公式
* in 输入金额, out 输出金额
* rIn tokenIn的流动性, rOut,tokenOut的流动性
* fee 手续费,注:当前带入0.997 也就是997/1000
*
* 两个计算公式实际是一样的, 只是一个求in,一个求out
* (rIn + in * f) * (rOut - out) = rIn * rOut
*
*
* 由out计算in
* (rIn + in * f) * (rOut - out) = rIn * rOut
* rIn * rOut + in * f * rOut - rIn * out - in * f * out = rIn * rOut
* rIn * out = in * f * rOut - in * f * out
* in = rIn * out / (f * (rOut - out)) + 1 (尾部的 +1应该是避免精度计算,最后一位小了,会成交不了)
*
*
* 由in计算out
* (rIn + in * f) * (rOut - out) = rIn * rOut
* rIn * rOut + in * f * rOut - rIn * out - in * f * out = rIn * rOut
* in * f * rOut = rIn * out + in * f * out
* out = in * f * rOut / rIn + in *f
*
*/
UniswapV2: K 校验手续费
//注:正常amount0 或者amount1有一个是0值
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
//其中一个不是0的转出
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
//闪电贷,略
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
//以下代码校验,查看下面说明
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
校验K推导说明
针对K值的校验计算
转入金额是实际带手续费的, out金额是in去掉手续费后,计算出来的out.
正常情况 (举例: 0in, 1 out) --即amount0out会是0,不需要转出
(交易前已经将0 in转入)
获取之前的流动性 r0,r1
转出out金额
获取交易对中两个地址的余额 b0,b1
amount0in = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;(0是in,0Out=0,所以前面的比较是true,结果是 )
amount0in = b0>r0 结果是 b0-r0,即实际进入金额(带手续费金额)
amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; (1是out)
正常是r1-out1=b1 所以走false,
得到 amount1In=0;
一定要这两个其中之一大于0
校验手续费
b0a = b0*1000 - amount0in * 3
b1a = b1*1000 - 0*3
req(b0a*b1a >= r0 * r1 * 1000 * 1000)
r0*r1是上一个k值,
公式 (rIn + in * f) * (rOut - out) = rIn * rOut
(b0-fee)*(b1) = r0*r1(上一次的k)
所以判断条件是(b0-fee)*(b1) >= r0*r1(上一次的k)
对比b0a,b1a 实际就是千3的手续费,没法用小数,所以两边都*1000,就可以用3计算
优惠劵
zgf1991
关注
关注
17
点赞
踩
97
收藏
觉得还不错?
一键收藏
知道了
48
评论
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
文章目录前提条件部署合约部署工厂和WETH合约部署路由合约(重要环节!!)步骤1 获取字节码步骤2 获得initCode步骤3 替换路由中的initCode当前部署结果部署前端其他补充(懂solidity的可以看看)添加流动性交换方法工具in/out计算公式推导参考链接 崔棉大师的教程手把手教你部署自己的uniswap交易所之前部署是跟着崔棉大师的教程走的,但是部署完了,没法实际使用,添加流动性还是交易会报错这里主要是做补充;前提条件自己有账号,且申请测试以太坊 (ropsten直接小狐狸 b
复制链接
扫一扫
专栏目录
智能合约USDT转入转出
07-11
智能合约USDT转入转出,简单逻辑
pragma solidity ^0.8.0;
//引入USDT
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
function allowance(address _owner, address spender) external view returns (uint256);
}
contract
在Sepolia测试网中部署uniswap合约
最新发布
sinat_33794480的博客
01-08
931
部署uniswap合约
48 条评论
您还未登录,请先
登录
后发表或查看评论
uniswap合约解读和部署
q_776355102的博客
01-21
1万+
了解uniswap
官方中文:http://uniswap.defiplot.com/#/swap
官方英文:https://app.uniswap.org
github源码:Uniswap · GitHub
如何工作:https://uniswap.org/docs/v2/protocol-overview/how-uniswap-works/
术语解释:https://uniswap.org/docs/v2/protocol-overview/glossary/
视频教程:手把手教你开发去
使用官方项目部署 Uniswap-v2
qq_41943430的博客
02-03
1918
使用官方项目部署 Uniswap-v2
BSC预售发布和教程.docx
01-07
教程仅供参考,请勿违法,谢谢配合。
QTUM链部署UniSwapV2
wb25409498的博客
11-21
363
regtest模式下,量子链部署UniSwapV2
UniswapV2代码中文注释
m0_60938243的博客
05-25
325
Uniswap V2是一种基于以太坊的去中心化交易协议,旨在提供快速、安全、无信任的代币交换服务。它是Uniswap协议的第二个版本,是对第一个版本的改进和升级。
在测试机部署react_手把手教你部署自己的uniswap交易所
weixin_34824510的博客
01-05
1559
本文作者:崔棉大师演示地址: https://fankouzu.github.io/cuiswap/ (请在测试网使用)准备Uniswap合约源码源码结构Uniswap在Github上面开源了全部合约代码,其中包括核心合约,周边合约两部分.Uniswap还开源了前端代码,前端代码使用React开发核心合约周边合约前端代码在Uniswap的核心代码中,主要包含3个合约:工厂合约,配对合约,ERC20...
二次开发uniswap-01-SDK
【03】的博客
03-11
2157
实现一个swap
uniswap源码+注释
wjl__ai__的博客
11-29
1万+
最近,看到一个比较厉害的机器人,每次夹人可以获取0.2个ETH,利润还是十分的诱人。
兴趣既然萌生了,那么也去研究了解一下。
那么,首先还是要读一下去中心化交易所的源码(uniswap)。
uniswap底层源码分三个模块,Factory,Pair,Router。
Factory存放在多个Pair,Router是通过调用Factory,Factory再来找到Pair,更加详细的交易逻辑,还得看底层源码。
Factory
pragma solidity =0.5.16;
import './interfa
bsc:BSC的前端开发人员,2015年版本
05-26
平衡计分卡
BSC的前端开发人员,2015年版本
安装
git clone https://github.com/evaluecz/bsc.git
cd bsc
npm install
启动应用程序
npm start app.js 然后,该应用程序在
量角器测试
在test目录中protractor test/protractor-cfg.js
Pancakeswap 和 Uniswap 交易客户端
03-19
Pancakeswap 和 Uniswap 交易客户端
Pancakeswap 和 Uniswap 交易客户端(和机器人),带有市价单、限价单、止损、自定义 gas 策略、GUI 等等。
如果您有任何问题或疑问,或想要此机器人的完整版本,您可以通过 Discord 联系我的支持:AviddotSupport#8131 或电报: 或 (请注意:这些不是机器人的制造商,他们提供支持) 。 另请查看网站:
变更日志 v2.0
添加了多个 DEX(Pcs v1、Uniswap v2)
强制买入和强制卖出按钮,当点击它时,它会以您选择的设置买入或卖出(不包括限价)
速度提升
许多,许多错误修复(不再强制关闭或不工作的令牌)
添加 USDT 作为主币选项
该程序现在自动确定令牌的名称和小数
先决条件
一个以太坊/bsc 地址
一台 Windows 机器(即将添加对 Mac OS 和 Linu
BSC、HECO通用限制买卖合约
12-29
可动态设置买入次数卖出次数
uniswap v2 代码解读注释
热门推荐
zgf1991 IT新人
07-16
1万+
文章目录注意事项UniswapV2FactoryUniswapV2Router02uniswap 闪电贷(待补充)uniswap 添加/移除lp时手续费公式(待补充)
注意事项
部分说明
首次添加和二次添加lp算法有点差别
查找pair和创建pair,使用了create2,有需要可以具体了解
uniswap v2都是两个erc20组合成一个交易对,跟eth相关的内部都是使用的weth交易
交易方法中都是in/out,相对于pair, 方法中swapExactTokensForTokens,for的两侧带有
estimateGas gas required exceeds allowance (6989519) or always failing transaction
zgf1991 IT新人
06-10
1万+
在使用estimateGas计算gas的时候报错,
Node error: {"code":-32000,"message":"gas required exceeds allowance (6989519) or always failing transaction"}
开始都是参考官网的例子
var result = web3.eth.estimateGas({
to: "0x...
以太坊签名,验证签名, EIP712domain Permit授权并转账
zgf1991 IT新人
01-27
9526
文章目录一、Dapp 验签登录二、token EIP712DomainDomain 格式Permit 格式如何签名node签名网页小狐狸签名根据Dai的代码修改的demo
需求:
dapp 签名/验签登录 主要针对中心化接口鉴权;小狐狸签名时最好能让用户看到签名内容
学习EIP712Domain
一、Dapp 验签登录
参考链接
第二十九课 如何实现MetaMask签名授权后DAPP一键登录功能?
以太坊签名数据以及验证
两种签名
1、直接对内容签名(小狐狸可以看到hello)
web3.persona
remix Gas estimation failed
zgf1991 IT新人
05-29
5023
使用remix调用合约的approve方法给予授权,第一次没啥问题,之后都提示Gas estimation failed,以为gas给少了。 搞了一下午,后面注意到还有两个方法decreaseApproval,increaseApproval,发现使用后授权量有改变...
这个帖子给了点提示【Solidity】Gas estimation failed-Gas estimation erro...
bsc heco eth浏览器开源智能合约代码,图文说明
zgf1991 IT新人
03-02
4668
文章目录配置基本配置其他配置library构造参数方式一方式二开源流程示例情况1 基本配置都正确,没有构造参数和library情况2 补上library,不填构造参数情况3 填写构造参数(一般可以通过,也可能无法通过)情况4 从浏览器提示中取构造参数其他
在浏览器上开源合约代码,会碰到一些常见问题,这里做一个汇总以及相关解决方案
配置
基本配置
以下基本配置必须一样
编译版本 (这个指的是remix编译的版本,而不是合约代码里面写的版本)
EVM 版本 (一般都是default,如果有需要选了版本时,开源
uniapp 合约转账bsc
06-13
要使用 Uni-app 进行合约转账操作,需要先安装 web3.js,然后在代码中引入该库。接着,你需要连接到 BSC 网络,可以使用 Infura 提供的节点。
下面是一个示例代码:
```javascript
import Web3 from 'web3';
// 连接到 BSC 网络
const web3 = new Web3(new Web3.providers.HttpProvider('https://bsc-dataseed1.binance.org:443'));
// 合约地址和 ABI
const contractAddress = '0x0000000000000000000000000000000000000000';
const contractAbi = [{...}];
// 创建合约实例
const contract = new web3.eth.Contract(contractAbi, contractAddress);
// 转账
const fromAddress = '0x...'; // 发送方地址
const privateKey = '0x...'; // 发送方私钥
const recipientAddress = '0x...'; // 接收方地址
const amount = web3.utils.toWei('1', 'ether'); // 转账金额
const tx = {
from: fromAddress,
to: contractAddress,
gas: 200000,
data: contract.methods.transfer(recipientAddress, amount).encodeABI()
};
web3.eth.accounts.signTransaction(tx, privateKey).then(signed => {
web3.eth.sendSignedTransaction(signed.rawTransaction).on('receipt', console.log);
});
```
其中,`contractAddress` 和 `contractAbi` 分别是你要调用的合约的地址和 ABI。`fromAddress` 是发送方的地址,`privateKey` 是发送方的私钥,`recipientAddress` 是接收方的地址,`amount` 是转账金额,需要将其转化为 wei 单位。
最后,使用 `web3.eth.sendSignedTransaction` 方法发送签名后的交易,通过监听 `receipt` 事件获取交易的结果。
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
zgf1991
CSDN认证博客专家
CSDN认证企业博客
码龄14年
暂无认证
98
原创
6万+
周排名
55万+
总排名
87万+
访问
等级
8543
积分
151
粉丝
103
获赞
221
评论
299
收藏
私信
关注
热门文章
android 通过http网络下载图片 并
32034
Android 中 使用 Gson解析json
24364
Caused by: java.lang.IllegalArgumentException: the bind value at index 1 is null
23535
Android中Paint字体属性的设置
22888
百度地图获取自己位置
22068
分类专栏
filecoin-lotus
3篇
android
39篇
android 百度地图
3篇
java基础学习
7篇
正则
微信小程序
GO
5篇
以太坊
7篇
nodejs
1篇
最新评论
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
zgf1991:
没试过,都是一样的,按照指定版本应该还是可以的,
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
一个Arno:
大佬有尝试过部署到arbi上嘛,我按照文章中的方法来了好像不行欸
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
zgf1991:
情况太多了, 是不是没授权
完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
agys14:
主币(bnb)可以兑换代币,代币不能兑换主币是什么原因?
solidity基础以及版本变化
zale013:
合约那么重要得每个细节搞懂
您愿意向朋友推荐“博客详情页”吗?
强烈不推荐
不推荐
一般般
推荐
强烈推荐
提交
最新文章
bsc heco eth浏览器开源智能合约代码,图文说明
solidity基础以及版本变化
uniswap v2 代码解读注释
2022年2篇
2021年2篇
2020年6篇
2019年7篇
2018年3篇
2017年5篇
2016年1篇
2015年14篇
2014年24篇
2013年36篇
2012年56篇
2011年6篇
目录
目录
分类专栏
filecoin-lotus
3篇
android
39篇
android 百度地图
3篇
java基础学习
7篇
正则
微信小程序
GO
5篇
以太坊
7篇
nodejs
1篇
目录
评论 48
被折叠的 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
个
红包个数最小为10个
红包总金额
元
红包金额最低5元
余额支付
当前余额3.43元
前往充值 >
需支付:10.00元
取消
确定
下一步
知道了
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom 发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额
0
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。
余额充值
手把手教你部署自己的uniswap交易所 - 知乎
手把手教你部署自己的uniswap交易所 - 知乎首发于登链社区切换模式写文章登录/注册手把手教你部署自己的uniswap交易所登链社区区块链技术爱好者的家园本文作者:崔棉大师演示地址: https://fankouzu.github.io/cuiswap/ (请在测试网使用)准备Uniswap合约源码源码结构Uniswap在Github上面开源了全部合约代码,其中包括核心合约,周边合约两部分.Uniswap还开源了前端代码,前端代码使用React开发核心合约周边合约前端代码在Uniswap的核心代码中,主要包含3个合约:工厂合约,配对合约,ERC20合约.其中配对合约继承了ERC20合约,我们可以把它们看作一个合约.工厂合约通过create2方法部署配对合约,所以在部署合约时只需要部署工厂合约.周边合约中包括一些示例代码,例如价格预言机,闪电交换,其中最重要的是路由合约.在周边合约的代码库中,包含两个路由合约:UnsiwapV2Router01,UnsiwapV2Router02.工厂合约和配对合约需要通过路由合约调用才能更好的完成交易所的全部功能,所以我们还要部署路由合约两个合约大部分相同,有小部分不同,如果将两个合约的差异化合并成一个合约,部署的时候将会出现out of gas,所以才被分成了两个合约.常用功能两个合约中都包括,所以我们部署其中任意一个路由合约都可以继承引用调用ERC20合约配对合约工厂合约路由合约从浏览器中下载合约源码如果你对合约代码并不熟悉,也可以跳过上面这部分,接下来我们将从以太坊浏览器中直接拷贝线上版合约源码工厂合约路由合约01[可选]路由合约02部署合约准备部署账户Uniswap的路由合约部署在以太坊的主网和Ropsten,Rinkeby,Goerli,Kovan几个测试网的合约地址都是相同的,这样可以使Uniswap的前端不管切换到任何一个网络,路由地址都不会变.要想实现这个相同地址的部署,我们需要准备一个全新的账户用来部署合约.全新的账户指的是在部署合约之前的nonce值为0.因为合约的地址是根据你的账户地址和nonce值计算出来的,所以在不同网络中,如果nonce值相同,部署出的合约地址也相同.通过助记词生成新账户可以通过我之前录制的视频学习操作方法B站视频油管视频生成好助记词之后,记得用英文助记词,保存好助记词,还有助记词对应的账户地址向新地址转帐ETH部署合约需要的gas费约为0.18个Ether,目前主网可能需要的更多.通过一个已有Ether的账户向新账户转帐.测试网的Ether可以通过每个测试网的水龙头申请到测试币.获取测试币方法转账完成后,将助记词导入到Metamask中准备WETH合约地址在部署路由合约时,构造函数中需要填入工厂合约的地址和WETH合约的地址,由于WETH合约的地址在主网和测试网的地址都不相同,所以需要找到每个网络中WETH合约的地址.WETH合约用于将Eth交换为erc20的Eth,由于Eth不是erc20的token,所以我们必须使用WETH作为交换媒介{
mainnet:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
ropsten:'0xc778417E063141139Fce010982780140Aa0cD5Ab',
rinkeby:'0xc778417E063141139Fce010982780140Aa0cD5Ab',
goerli:'0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
kovan:'0xd0A1E359811322d97991E03f863a0C30C2cF029C'
}申请infuraKey在部署合约之前,我们还需要使用infura作为免费节点,所以需要申请一个infuraKey申请地址:infura.io申请方法使用remix部署合约将工厂合约和路由合约的线上版本导入到remix中,在编译合约的选项中,EVM VERSION选择istanbul,COMPILER CONFIGURATION中选择Enable optimization部署顺序和构造函数部署工厂合约构造函数的参数是一个自己常用的账户地址部署路由合约01 [可选], 02构造函数的参数1是工厂合约的地址参数2 是当前网络中WETH合约的地址,参考前文部署Uniswap前端克隆前端代码在项目目录运行命令:$ git clone https://github.com/Uniswap/uniswap-interface.git安装依赖库在项目目录运行命令:$ cd uniswap-interface
$ yarn安装完成后,可以先测试运行一下,在uniswap-interface目录运行命令$ yarn start如果运行成功,将会打开一个浏览器,同时打开Uniswap的前端界面修改路由地址在Uniswap的前端中以常量的形式定义了Uniswap的路由地址,我们只需要修改路由地址就可以让前端链接到你的路由合约中修改文件: 项目目录/uniswap-interface/src/constants/index.ts 第6行import { AbstractConnector } from '@web3-react/abstract-connector'
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' //修改成你的路由合约地址
......保存后运行yarn start即可看到效果将代码部署到GitHub Pages创建GitHub项目创建项目的方法就不在这里讲了,不会的同学可以去搜索一下将前端代码添加到GitHub项目仓库首先要删除原先Uniswap项目中的.git目录,在项目目录运行命令:$ cd uniswap-interface
$ rm -rf .git然后初始化git,并将Unsiwap前端代码添加到自己的项目仓库中git init
git remote add origin https://github.com/用户名/项目名.git安装并部署gh-pages我们将通过gh-pages模块将前端代码部署到github.io,在前端代码的目录运行:$ yarn add gh-pages接下来要编译react和部署gh-pages,在前端代码的目录运行:$ yarn build修改前端代码目录中的package.json$ vim package.json
{
"name": "@uniswap/interface",
"description": "Uniswap Interface",
"homepage": "https://用户名.github.io/项目名称",//修改这里
......
// 添加部署的脚本,还是在package.json中
......
"scripts": {
......
"deploy": "gh-pages -d build" //添加这一行
},保存退出之后,在前端代码的目录运行:$ git add .
$ git commit -m "first commit"
$ git push
$ yarn deploy现在在浏览器中打开https://用户名.github.io/项目名称/index.html就可以打开自己的交易所啦.如果不输入地址结尾的index.html在项目刚部署之后会报错,过一段时间就可以不输入了.扩展部署自己的weth可以将以太坊浏览器中的weth源码拷贝下来,自己部署一个属于自己的weth合约可信token列表Uniswap有一个自己的可信token列表,同样被设置在项目目录/uniswap-interface/src/constants/index.ts文件中,在最后一行就是.你可以将这个链接地址的文件拷贝下来,设置成自己需要的可信token地址列表,然后上传到github目录中,再修改index.ts文件中的链接地址,这样就可以让你自己的交易所中拥有自己设置的可信token列表了原文链接:https://learnblockchain.cn...作者主页:https://learnblockchain.cn... ,欢迎阅读作者更多好文登链社区-区块链技术爱好者的家园发布于 2020-09-02 15:19交易所区块链技术赞同 162 条评论分享喜欢收藏申请转载文章被以下专栏收录登链社区高质量区块链技术文