以太坊|完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)


文章目录

      • 前提条件
      • 部署合约
        • 部署工厂和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
注意事项
以太坊|完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
文章图片

部署工厂合约和路由合约时,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的字节码, 看下图
以太坊|完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
文章图片

获得类似这样的
得到以下结构的内容, 只需要object字段的内容; 复制 { "linkReferences": {}, "object": "取这里的内容", "opcodes": "-", "sourceMap": "-" }

步骤2 获得initCode 打开网址 http://emn178.github.io/online-tools/keccak_256.html
将刚才得到的object字段内容粘贴,选择input type HEX
如下图
以太坊|完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)
文章图片

步骤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); }

【以太坊|完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)】校验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计算

    推荐阅读