完整部署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)_第1张图片

部署工厂合约和路由合约时,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)_第2张图片

获得类似这样的

得到以下结构的内容, 只需要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张图片

步骤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计算

你可能感兴趣的