{"content":{"title":"uniswap v2 代码解读","body":"## 前言：\r\n\r\nUniswap V2代码结构\r\n\r\nUniswap智能合约代码由两个github项目组成。一个是core，一个是periphery。\r\n\r\nhttps://github.com/Uniswap/uniswap-v2-core.git\r\n\r\nhttps://github.com/Uniswap/uniswap-v2-periphery.git\r\n\r\ncore偏核心逻辑，单个swap的逻辑。periphery偏外围服务，一个个swap的基础上构建服务。单个swap，两种代币形成的交易对，俗称“池子”。每个交易对有一些基本属性：reserve0/reserve1以及total supply。reserve0/reserve1是交易对的两种代币的储存量。total supply是当前流动性代币的总量。每个交易对都对应一个流动性代币（LPT - liquidity provider token）。简单的说，LPT记录了所有流动性提供者的贡献。所有流动性代币的总和就是total supply。Uniswap协议的思想是reserve0*reserve1的乘积不变。\r\n\r\nPeriphery逻辑\r\n\r\n核心逻辑实现在UniswapV2Router02.sol中。称为Router，因为Periphery实现了“路由”，支持各个swap之间的连接。基本上实现了三个功能：1/ add liquidity（增加流动性）2/remove liqudity (抽取流动性) 3/ swap（交换）。\r\n\r\n***uniswap v2非常重要，必须要非常熟悉该协议！！！***\r\n\r\n以下便是对uniswap v2 代码的解读。\r\n\r\n## v2-core\r\n\r\n### 1.  UniswapV2ERC20\r\n\r\nuniswap v2的代币实际上是 ERC20代币。实现ERC20标准方法。\r\n\r\n**代码解读如下：**\r\n\r\n```solidity\r\npragma solidity =0.5.16;\r\n\r\nimport './interfaces/IUniswapV2ERC20.sol';\r\nimport './libraries/SafeMath.sol';\r\n\r\ncontract UniswapV2ERC20 is IUniswapV2ERC20 {\r\n    using SafeMath for uint; // 将 SafeMath 库合约用于 uint 类型\r\n\r\n    string public constant name = 'Uniswap V2'; // 代币的名字\r\n    string public constant symbol = 'UNI-V2'; // 代币符号\r\n    uint8 public constant decimals = 18;\r\n    uint  public totalSupply; // 发行量\r\n    \r\n    // 存储某地址的代币余额，address => uint的映射\r\n    mapping(address => uint) public balanceOf; \r\n\r\n    // 存储某一地址对另一地址的代币授权量，授权之后可以允许被授权人使用授权人的代币进行转账 `transferFrom`\r\n    mapping(address => mapping(address => uint)) public allowance; \r\n\r\n    // \r\n    bytes32 public DOMAIN_SEPARATOR;\r\n\r\n    // keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\r\n    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;\r\n    \r\n    // \r\n    mapping(address => uint) public nonces;\r\n\r\n    // 两个在授权和转账时会被触发的事件\r\n    event Approval(address indexed owner, address indexed spender, uint value);\r\n    event Transfer(address indexed from, address indexed to, uint value);\r\n\r\n    constructor() public {\r\n        uint chainId;\r\n        assembly {\r\n            // chainid指令用于获取当前区块链的链ID, 它唯一地标识了当前区块链的网络\r\n            chainId := chainid\r\n        }\r\n         // 初始化 DOMAIN_SEPARATOR 变量\r\n        DOMAIN_SEPARATOR = keccak256(\r\n            abi.encode(\r\n                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),\r\n                keccak256(bytes(name)),\r\n                keccak256(bytes('1')),\r\n                chainId,\r\n                address(this)\r\n            )\r\n        );\r\n    }\r\n\r\n    /**\r\n        1. 铸币操作，因为有库函数的引用，可以直接调用 add,sub等运算\r\n        2. totalSupply 发行量时对于整个系统来说，而balance是对于某个账户来说\r\n     */\r\n    function _mint(address to, uint value) internal {\r\n        totalSupply = totalSupply.add(value); // 发行量累加 value\r\n        balanceOf[to] = balanceOf[to].add(value); // to账户的 余额累加 value\r\n        emit Transfer(address(0), to, value); // 触发交易事件\r\n    }\r\n\r\n    /**\r\n        1. 销币操作\r\n     */\r\n    function _burn(address from, uint value) internal {\r\n        balanceOf[from] = balanceOf[from].sub(value); // from 账户的 余额累减 value\r\n        totalSupply = totalSupply.sub(value); // 发行量累减 value\r\n        emit Transfer(from, address(0), value); // 触发交易事件\r\n    }\r\n\r\n    /**\r\n        1. 授权操作\r\n        2. owner一般是调用者，spender则是授权者，value是授权代币量\r\n     */\r\n    function _approve(address owner, address spender, uint value) private {\r\n        // 记录owner 对spender 的授权量为 value\r\n        allowance[owner][spender] = value;\r\n        emit Approval(owner, spender, value); // 触发授权事件\r\n    }\r\n\r\n    /**\r\n        1. 转账操作\r\n        2. 内置函数_transfer(), from账户向to账户转移 value的代币\r\n        3. 而在外部函数中，transfer中的from为合约调用者\r\n     */\r\n    function _transfer(address from, address to, uint value) private {\r\n        // from 账户余额减少value\r\n        balanceOf[from] = balanceOf[from].sub(value);\r\n        // to 账户余额增加 value\r\n        balanceOf[to] = balanceOf[to].add(value);\r\n        emit Transfer(from, to, value);\r\n    }\r\n\r\n    /**\r\n        1. 外部授权操作\r\n        2. 外部授权规定了授权者是调用者，予以：'我'给spender授权\r\n     */\r\n    function approve(address spender, uint value) external returns (bool) {\r\n        _approve(msg.sender, spender, value);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n        1. 外部转账操作\r\n        2. 规定了转账者是调用者，予以：'我'给to转账value\r\n     */\r\n    function transfer(address to, uint value) external returns (bool) {\r\n        _transfer(msg.sender, to, value);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n        1. 外部转账操作，msg.sender是代理人\r\n        2. 在 pragma=0.5.16的版本中，uint(-1)= 115792089237316195423570985008687907853269984665640564039457584007913129639935\r\n        3. 要调用该函数，事先要让 from执行approve函数，给msg.sender授权\r\n        4. 最后调用_transfer()函数，执行from 向 to 转账 value的操作\r\n     */\r\n    function transferFrom(address from, address to, uint value) external returns (bool) {\r\n        if (allowance[from][msg.sender] != uint(-1)) {\r\n            allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);\r\n        }\r\n        _transfer(from, to, value);\r\n        return true;\r\n    }\r\n\r\n    /**\r\n        1. 许可操作\r\n        2. permit函数的作用是将代币授权给指定的目标地址，\r\n            使得目标地址可以代表代币持有人进行交易，而无需进行传统的授权交易。\r\n            这种新型的授权方法可以提高代币交易的效率和安全性，\r\n            同时也可以减少交易的成本和时间。\r\n        3. owner授权者，spender被授权者，value代币数目，deadline：授权的截止时间，必须在此时间之前完成授权\r\n           nonce：随机数，用于避免授权被重复使用，v、r、s：用于验证授权的签名参数。\r\n     */\r\n    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {\r\n        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');\r\n        bytes32 digest = keccak256(\r\n            abi.encodePacked(\r\n                '\\x19\\x01',\r\n                DOMAIN_SEPARATOR,\r\n                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\r\n            )\r\n        );\r\n        address recoveredAddress = ecrecover(digest, v, r, s);\r\n        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');\r\n        _approve(owner, spender, value);\r\n    }\r\n}\r\n\r\n```\r\n\r\n### 2. UniswapV2Factory\r\n\r\n工厂合约，用于创建Pair合约（以及设置协议手续费接收地址）\r\n\r\n**代码解读如下：**\r\n\r\n```solidity\r\npragma solidity =0.5.16;\r\n\r\nimport './interfaces/IUniswapV2Factory.sol';\r\nimport './UniswapV2Pair.sol';\r\n\r\ncontract UniswapV2Factory is IUniswapV2Factory {\r\n\r\n    address public feeTo; // 手续费接收地址\r\n    address public feeToSetter; // 手续费接收地址的设置者\r\n\r\n    /**\r\n        如果将 getPair设置为public，则编译的时候会在该合约中默认生成set 和get函数\r\n        解读：通过两个地址获取到交易对地址\r\n     */\r\n    mapping(address => mapping(address => address)) public getPair;\r\n\r\n    // 数组，存储所有交易对\r\n    address[] public allPairs;\r\n\r\n    // 交易对创建事件\r\n    event PairCreated(address indexed token0, address indexed token1, address pair, uint);\r\n\r\n    // 初始化，手续费接收地址的设置者\r\n    constructor(address _feeToSetter) public {\r\n        feeToSetter = _feeToSetter;\r\n    }\r\n\r\n    // 获取交易对的对数\r\n    function allPairsLength() external view returns (uint) {\r\n        return allPairs.length;\r\n    }\r\n\r\n    /**\r\n        首先将token0 token1按照顺序排序，确保token0字面地址小于token1。\r\n        接着使用assembly + create2创建合约。\r\n        assembly可以在Solidity中使用Yul语言直接操作EVM，是较底层的操作方法。\r\n        《Uniswap v2 白皮书》中讲到，create2主要用于创建确定性的交易对合约地址，\r\n        目的是根据两个代币地址直接计算pair地址，而无需调用链上合约查询。\r\n     */ \r\n    function createPair(address tokenA, address tokenB) external returns (address pair) {\r\n        // 两种代币地址不能相同\r\n        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');\r\n        // 先将token0 token1按照顺序排序，确保token0字面地址小于token1\r\n        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);\r\n        // 确保token0不等于 address(0)，则两个地址都不为 address(0)\r\n        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');\r\n        // 确保这两种代币地址的交易对为address(0),即这两个代币尚未创建交易对\r\n        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient\r\n        // 获取 `UniswapV2Pair`的字节码\r\n        bytes memory bytecode = type(UniswapV2Pair).creationCode;\r\n        // 生成盐salt，salt由这两个地址紧打包再hash获得，是唯一的\r\n        bytes32 salt = keccak256(abi.encodePacked(token0, token1));\r\n        // 通过creat2计算交易对地址\r\n        assembly {\r\n            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)\r\n        }      \r\n        // 将新生成的交易对中的两种代币设置为 token0, token1\r\n        IUniswapV2Pair(pair).initialize(token0, token1);\r\n        // 记录token0 和 token1生成的交易对\r\n        getPair[token0][token1] = pair;\r\n        // 反向填充映射\r\n        getPair[token1][token0] = pair; // populate mapping in the reverse direction\r\n        // 保存该交易对pair\r\n        allPairs.push(pair);\r\n        // 创建成功，触发交易对生成事件\r\n        emit PairCreated(token0, token1, pair, allPairs.length);\r\n    }\r\n\r\n\r\n    // 设置手续费接收地址\r\n    function setFeeTo(address _feeTo) external {\r\n        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');\r\n        feeTo = _feeTo;\r\n    }\r\n\r\n    // 修改手续费接收地址的设置者\r\n    function setFeeToSetter(address _feeToSetter) external {\r\n        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');\r\n        feeToSetter = _feeToSetter;\r\n    }\r\n}\r\n\r\n```\r\n\r\n### 3. UniswapV2Pair\r\n\r\n> Pair（交易对）合约，定义和交易有关的几个最基础方法，如swap/mint/burn，价格预言机等功能，其本身是一个ERC20合约，继承UniswapV2ERC20\r\n>\r\n> Pair合约主要实现了三个方法：mint（添加流动性）、burn（移除流动性）、swap（兑换）。\r\n\r\n**代码解读如下：**\r\n\r\n```solidity\r\npragma solidity =0.5.16;\r\n\r\nimport './interfaces/IUniswapV2Pair.sol';\r\nimport './UniswapV2ERC20.sol';\r\nimport './libraries/Math.sol';\r\nimport './libraries/UQ112x112.sol';\r\nimport './interfaces/IERC20.sol';\r\nimport './interfaces/IUniswapV2Factory.sol';\r\nimport './interfaces/IUniswapV2Callee.sol';\r\n\r\ncontract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {\r\n    // 引用库函数\r\n    using SafeMath  for uint;\r\n    using UQ112x112 for uint224;\r\n\r\n    // 最低额度的流动性\r\n    uint public constant MINIMUM_LIQUIDITY = 10**3;\r\n    // tansfer函数的选择器 \r\n    bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));\r\n\r\n    // 工厂？token0和token1交易对中的两种代币\r\n    address public factory;\r\n    address public token0;\r\n    address public token1;\r\n\r\n    // token0和token1交易对中的两种代币的存储量\r\n    uint112 private reserve0;           // uses single storage slot, accessible via getReserves\r\n    uint112 private reserve1;           // uses single storage slot, accessible via getReserves\r\n    // 上次更新的时间\r\n    uint32  private blockTimestampLast; // uses single storage slot, accessible via getReserves\r\n\r\n    // 累加交易价格\r\n    uint public price0CumulativeLast;\r\n    uint public price1CumulativeLast;\r\n    uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event\r\n\r\n    // 锁标志\r\n    uint private unlocked = 1;\r\n\r\n    // 修饰器为了防止异步\r\n    modifier lock() {\r\n        require(unlocked == 1, 'UniswapV2: LOCKED');\r\n        unlocked = 0;\r\n        _;\r\n        unlocked = 1;\r\n    }\r\n\r\n    // 读取交易对中两种代币的余额，以及上一次交易对更新的时间\r\n    function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {\r\n        _reserve0 = reserve0;\r\n        _reserve1 = reserve1;\r\n        _blockTimestampLast = blockTimestampLast;\r\n    }\r\n\r\n    /**\r\n        1. 通过call的方式，调用token中的 transfer 函数---兼容性更强\r\n        2. 判断调用是否成功，success是否为true，data是否为空\r\n     */\r\n    function _safeTransfer(address token, address to, uint value) private {\r\n        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));\r\n        require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');\r\n    }\r\n\r\n    event Mint(address indexed sender, uint amount0, uint amount1);\r\n    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\r\n    event Swap(\r\n        address indexed sender,\r\n        uint amount0In,\r\n        uint amount1In,\r\n        uint amount0Out,\r\n        uint amount1Out,\r\n        address indexed to\r\n    );\r\n    event Sync(uint112 reserve0, uint112 reserve1);\r\n\r\n    // 初始化 工厂地址为调用者\r\n    constructor() public {\r\n        factory = msg.sender;\r\n    }\r\n\r\n    // called once by the factory at time of deployment\r\n    // 初始化交易对中的两种代币地址\r\n    function initialize(address _token0, address _token1) external {\r\n        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check\r\n        token0 = _token0;\r\n        token1 = _token1;\r\n    }\r\n\r\n    // update reserves and, on the first call per block, price accumulators\r\n    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {\r\n        require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');\r\n        // 记录当前更新时间\r\n        uint32 blockTimestamp = uint32(block.timestamp % 2**32);\r\n        // 记录过去了多久\r\n        uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired\r\n        // 计算出当前的交易价格\r\n        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {\r\n            // * never overflows, and + overflow is desired\r\n            price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;\r\n            price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;\r\n        }\r\n        // 将这两种代币的存储量设置为代币的余额\r\n        reserve0 = uint112(balance0);\r\n        reserve1 = uint112(balance1);\r\n        // 更新当前操作时间\r\n        blockTimestampLast = blockTimestamp;\r\n        // 触发同步事件\r\n        emit Sync(reserve0, reserve1);\r\n    }\r\n\r\n    // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)\r\n    // 计算铸币手续费\r\n    function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {\r\n        address feeTo = IUniswapV2Factory(factory).feeTo(); // 获取当前factory铸币手续费的接收地址\r\n        feeOn = feeTo != address(0); // 检查该factory是否设置了手续费接收地址\r\n        uint _kLast = kLast; // gas savings\r\n        if (feeOn) { // 如果该factory有手续费接收地址\r\n            if (_kLast != 0) {\r\n                uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));\r\n                uint rootKLast = Math.sqrt(_kLast);\r\n                if (rootK > rootKLast) {\r\n                    uint numerator = totalSupply.mul(rootK.sub(rootKLast));\r\n                    uint denominator = rootK.mul(5).add(rootKLast);\r\n                    uint liquidity = numerator / denominator;\r\n                    if (liquidity > 0) _mint(feeTo, liquidity); // 给feeTo地址铸币liquidity\r\n                }\r\n            }\r\n        } else if (_kLast != 0) {\r\n            kLast = 0;\r\n        }\r\n    }\r\n\r\n    // this low-level function should be called from a contract which performs important safety checks\r\n    // 铸币操作，添加流动性\r\n    function mint(address to) external lock returns (uint liquidity) {\r\n        // 读取代币的存储量\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        // 获取这两种代币的余额\r\n        uint balance0 = IERC20(token0).balanceOf(address(this));\r\n        uint balance1 = IERC20(token1).balanceOf(address(this));\r\n        // 计算当前合约中两个代币的净增量，并赋值给amount0和amount1变量\r\n        // 净增量等于余额减去储备量\r\n        uint amount0 = balance0.sub(_reserve0);\r\n        uint amount1 = balance1.sub(_reserve1);\r\n        // 调用_mintFee函数，计算是否需要收取协议手续费，并返回一个布尔值，赋值给feeOn变量\r\n        bool feeOn = _mintFee(_reserve0, _reserve1); // 理解为支付完铸币费用\r\n        // 记录发行量，难道这里也讲究异步的现象？\r\n        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee\r\n        \r\n        // 如果是首次提供该交易对的流动性，则根据根号xy生成流动性代币，并销毁其中的MINIMUM_LIQUIDITY（即1000wei\r\n        if (_totalSupply == 0) {\r\n            // 计算流动性代币的数量，等于两个代币净增量乘积的平方根减去最小流动性常量，并赋值给liquidity变量\r\n            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);\r\n            // 调用_mint函数，向零地址铸造最小流动性常量数量的流动性代币（永久锁定）\r\n           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens\r\n        } else {\r\n            // 计算流动性代币的数量，等于两个代币净增量与储备量比例乘积与总供应量乘积的较小值，并赋值给liquidity变量\r\n            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);\r\n        }\r\n        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');\r\n        _mint(to, liquidity); // 为to铸币，liquidity\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1); // 调用_update函数，更新当前合约中两个代币的储备量为最新的余额\r\n        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\r\n        emit Mint(msg.sender, amount0, amount1);\r\n    }\r\n\r\n    // this low-level function should be called from a contract which performs important safety checks\r\n    // 销币操作，移除流动性\r\n    function burn(address to) external lock returns (uint amount0, uint amount1) {\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        address _token0 = token0;                                // gas savings\r\n        address _token1 = token1;                                // gas savings\r\n        uint balance0 = IERC20(_token0).balanceOf(address(this));\r\n        uint balance1 = IERC20(_token1).balanceOf(address(this));\r\n        uint liquidity = balanceOf[address(this)];\r\n\r\n        // 参考白皮书，为了节省交易手续费，Uniswap v2只在mint/burn流动性时收取累计的协议手续费。\r\n        bool feeOn = _mintFee(_reserve0, _reserve1);\r\n        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee\r\n        // 采用mint中计算 liquidity的方法倒推amount\r\n        amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution\r\n        amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution\r\n        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');\r\n        // 销毁本合约的 流动性 liquidity\r\n        _burn(address(this), liquidity);\r\n        // 调用_token0(_token1)中的 transfer函数\r\n\r\n        // 通过各自token中实现的transfer将token转移回to\r\n        _safeTransfer(_token0, to, amount0);\r\n        _safeTransfer(_token1, to, amount1);\r\n        //更新合约自身的token0、1余额\r\n        balance0 = IERC20(_token0).balanceOf(address(this));\r\n        balance1 = IERC20(_token1).balanceOf(address(this));\r\n        //更新池子中的储备量和价格积累器\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\r\n        emit Burn(msg.sender, amount0, amount1, to);\r\n    }\r\n\r\n    // this low-level function should be called from a contract which performs important safety checks\r\n   \r\n   /**\r\n        实现两种代币的交换（交易）功能-----闪电贷功能\r\n            uint amount0Out：要转换的第一种代币的数量。\r\n            uint amount1Out：要转换的第二种代币的数量。\r\n            address to：接收转换后代币的目标地址。\r\n            bytes data：可选的额外数据，用于向目标地址提供更多信息。\r\n   */  \r\n   // 接收者 to 必须要实现 `uniswapV2Call`函数，通过此函数输入交换的代币\r\n    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {\r\n\r\n        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        // 检查输出量是否小于储备量\r\n        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');\r\n\r\n        uint balance0;\r\n        uint balance1;\r\n\r\n        { // scope for _token{0,1}, avoids stack too deep errors\r\n        address _token0 = token0;\r\n        address _token1 = token1;\r\n        // 检查接收地址是否合法\r\n        require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');\r\n\r\n        if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens\r\n        if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens\r\n        // 如果有额外数据，就调用接收地址的回调函数\r\n        if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);\r\n\r\n        balance0 = IERC20(_token0).balanceOf(address(this));\r\n        balance1 = IERC20(_token1).balanceOf(address(this));\r\n        }\r\n        // 计算输入量\r\n        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;\r\n        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;\r\n\r\n        require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');\r\n\r\n        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors\r\n        // 计算调整后的余额，因为在solidity中没有浮点数 0.3%，这样是为了模拟出 0.3%\r\n        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));\r\n        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));\r\n        require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');\r\n        }\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);\r\n    }\r\n\r\n    // force balances to match reserves\r\n    /**\r\n        用于移除资金池中多余代币的函数。它的作用是将代币池中多余的代币转移到指定的目标地址，以便于在资金池中保持正确的代币比例\r\n     */\r\n    function skim(address to) external lock {\r\n        address _token0 = token0; // gas savings\r\n        address _token1 = token1; // gas savings\r\n        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));\r\n        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));\r\n    }\r\n\r\n    // force reserves to match balances\r\n    // 同步，迫使代币余额与代币储量相匹配，调用_update函数\r\n    function sync() external lock {\r\n        _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);\r\n    }\r\n}\r\n```\r\n> **分析 `mint`函数，举例**\r\n>\r\n> 假设有一个流动性池，其中有 10 个 ETH 和 5000 个 USDC，总供应量为 70.71 个流动性代币（这里面包括过去交易留下的Fees）。现在，有人想向这个池中添加 1 个 ETH 和 500 个 USDC，以获得更多的流动性代币。那么，他们将获得多少流动性代币呢？\r\n>\r\n> 首先，我们需要计算添加后的两种代币的储备量和余额。**储备量等于原来的储备量加上添加的数量，余额等于储备量加上协议手续费（假设为 0.3%）。**因此，我们有：\r\n>\r\n> - 储备量：reserve0 = 10 + 1 = 11 ETH，reserve1 = 5000 + 500 = 5500 USDC\r\n> - 余额：balance0 = reserve0 * (1 + 0.003) = 11.033 ETH，balance1 = reserve1 * (1 + 0.003) = 5516.5 USDC\r\n>\r\n> 接下来，我们需要计算添加后的总供应量。如果是第一次添加流动性，则使用公式 sqrt(x * y) — MINIMUM_LIQUIDITY，否则使用公式 min(x * totalSupply / reserve0, y * totalSupply / reserve1)。因为这不是第一次添加流动性，所以我们使用后者。因此，我们有：\r\n>\r\n> - 总供应量：totalSupply = min(balance0 * 70.71 / reserve0, balance1 * 70.71 / reserve1) = min(71.41, 71.41) = 71.41\r\n>\r\n> 最后，我们需要计算添加者获得的流动性代币的数量。这个数量等于添加后的总供应量减去添加前的总供应量。因此，我们有：\r\n>\r\n> - 流动性代币：liquidity = totalSupply — 70.71 = 71.41–70.71 = 0.7\r\n>\r\n> 也就是说，添加者将获得大约 0.7 个流动性代币。\r\n>\r\n> **分析`burn`**,举例\r\n>\r\n> 假设有一个池子，它允许交易 ETH 和 DAI，它的地址是 `0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11`。这个池子有以下的状态：\r\n>\r\n> - 流动性代币的总供应量是 `3,000,000`。\r\n> - 池子里有 `10,000` 个 ETH 和 `4,000,000` 个 DAI。\r\n> - 协议费用是 `0.05%`。\r\n> - 你拥有 `30,000` 个流动性代币，也就是池子的 `1%`。\r\n>\r\n> 现在，你想要退出这个池子，把你的流动性代币销毁，并拿回你的 ETH 和 DAI。你可以调用这个函数，把 `to` 参数设为你自己的地址。这样，函数会做以下的事情：\r\n>\r\n> - 它会从池子里获取 ETH 和 DAI 的储备量，分别是 `_reserve0 = 10,000` 和 `_reserve1 = 4,000,000`。\r\n> - 它会获取 ETH 和 DAI 的地址，分别是 `_token0 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` 和 `_token1 = 0x6B175474E89094C44Da98b954EedeAC495271d0F`。\r\n> - 它会获取合约自身的 ETH 和 DAI 的余额，分别是 `balance0 = 10,000` 和 `balance1 = 4,000,000`。注意，这里假设没有其他人在同一区块内与池子交互，否则余额可能会有变化。\r\n> - 它会获取合约自身的流动性代币余额，也就是 `liquidity = 30,000`。\r\n> - 它会调用 `_mintFee` 函数，来分发协议费用给流动性提供者。假设在你加入池子后，没有发生过任何交易，那么协议费用就是零，所以 `_mintFee` 函数不会改变任何东西，并返回 `feeOn = false`。\r\n> - 它会计算你能够拿回的 ETH 和 DAI 的数量，分别是 `amount0 = liquidity * balance0 / totalSupply = 30,000 * 10,000 / 3,000,000 = 100` 和 `amount1 = liquidity * balance1 / totalSupply = 30,000 * 4,000,000 / 3,000,000 = 40,000`。这保证了按比例分配。\r\n\r\n>  **知识点：**\r\n>\r\n> `abi.decode(data, (bool))`是Solidity中的一种函数调用，用于将字节数组（byte array）解码为布尔值（bool）类型。\r\n>\r\n> 具体来说，`abi.decode`函数接受两个参数：字节数组和数据类型。在这里，字节数组是要解码的数据，数据类型是要解码成的目标类型，即布尔值。\r\n>\r\n> 函数调用`abi.decode(data, (bool))`将字节数组解码为一个布尔值。这个布尔值的值取决于字节数组中的数据。如果字节数组中的数据为0，则解码后的布尔值为false，否则为true。\r\n\r\n> **知识点：**\r\n>\r\n> UQ112x112.encode(_reserve1).uqdiv(_reserve0)\r\n>\r\n> `UQ112x112.encode(_reserve1).uqdiv(_reserve0)`是Uniswap V2中的一个计算交易价格的操作，使用了UQ112x112固定点数算法。\r\n>\r\n> 具体来说，`_reserve0`和`_reserve1`是Uniswap V2交易对中两种资产的余额（reserve），`UQ112x112.encode()`函数将余额编码为UQ112x112固定点数格式，然后使用了`.uqdiv()`函数对两种资产的余额进行了除法操作，计算出当前的交易价格。\r\n>\r\n> UQ112x112固定点数算法是一种用于在以太坊合约中进行精确数学计算的算法。它将浮点数转换为整数，并使用固定的小数位数进行计算。在Uniswap V2中，UQ112x112固定点数算法被广泛应用于计算交易价格和资金池分配等。\r\n>\r\n> 在这个操作中，`UQ112x112.encode(_reserve1)`将`_reserve1`编码为UQ112x112格式，然后`.uqdiv(_reserve0)`将编码后的`_reserve1`除以`_reserve0`。最终的结果是一个UQ112x112格式的数，表示当前的交易价格。\r\n\r\n## v2-periphery\r\n\r\n### 1. UniswapV2Router02\r\n\r\n```solidity\r\npragma solidity =0.6.6;\r\n\r\nimport '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';\r\nimport '@uniswap/lib/contracts/libraries/TransferHelper.sol';\r\n\r\nimport './interfaces/IUniswapV2Router02.sol';\r\nimport './libraries/UniswapV2Library.sol';\r\nimport './libraries/SafeMath.sol';\r\nimport './interfaces/IERC20.sol';\r\nimport './interfaces/IWETH.sol';\r\n\r\ncontract UniswapV2Router02 is IUniswapV2Router02 {\r\n    using SafeMath for uint;\r\n\r\n    address public immutable override factory;\r\n    address public immutable override WETH;\r\n\r\n    // 修饰器确保 操作在截止日期之前\r\n    modifier ensure(uint deadline) {\r\n        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');\r\n        _;\r\n    }\r\n\r\n    // 初始化factory 和 WETH地址\r\n    constructor(address _factory, address _WETH) public {\r\n        factory = _factory;\r\n        WETH = _WETH;\r\n    }\r\n\r\n    // 接收ETH\r\n    receive() external payable {\r\n        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract\r\n    }\r\n\r\n    // **** ADD LIQUIDITY ****\r\n    /**  _addLiquidity可以帮助计算最佳汇率。如果是首次添加流动性，则会先创建交易对合约；\r\n        否则根据当前池子余额计算应该注入的最佳代币数量。 \r\n    */\r\n    function _addLiquidity(\r\n        address tokenA, // 代币A\r\n        address tokenB, // 代币B\r\n        uint amountADesired, // 希望存入的代币A数量\r\n        uint amountBDesired, // 希望存入的代币B数量\r\n        uint amountAMin, // 最少存入的代币A数量\r\n        uint amountBMin // 最少存入的代币B数量\r\n    ) internal virtual returns (uint amountA, uint amountB) {\r\n        // create the pair if it doesn't exist yet\r\n        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {\r\n            IUniswapV2Factory(factory).createPair(tokenA, tokenB);\r\n        }\r\n        // 获取交易对中代币A B的存储量\r\n        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);\r\n        // 如果储备量都为 0，那两个预期支付额就是成交量\r\n        if (reserveA == 0 && reserveB == 0) {\r\n            (amountA, amountB) = (amountADesired, amountBDesired);\r\n        } else {\r\n            // 采用控制变量法算出最佳的汇率\r\n\r\n            /**\r\n                如果计算得出的结果值 amountBOptimal 不比 amountBDesired 大，且不会小于 amountBMin，\r\n                就可将 amountADesired 和该 amountBOptimal 作为结果值返回。\r\n                如果 amountBOptimal 大于 amountBDesired，则根据 amountBDesired 计算得出需要支付多少 tokenA，\r\n                得到 amountAOptimal，只要 amountAOptimal 不大于 amountADesired 且不会小于 amountAMin，\r\n                就可将 amountAOptimal 和 amountBDesired 作为结果值返回。\r\n             */\r\n\r\n            // 调用quote函数，换算amountADesired 对应的B代币为多少\r\n            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);\r\n            \r\n            // 最佳的B代币数量少于希望存入的代币B数量\r\n            if (amountBOptimal <= amountBDesired) {\r\n                // 这是定 amountADesired，求B的最佳数量\r\n                // 而且最佳B代币数量要求 >= amountBMin\r\n                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');\r\n                // 返回希望存入的A，和最佳的B数量\r\n                (amountA, amountB) = (amountADesired, amountBOptimal);\r\n            } else {\r\n                // 定amountBDesired，求A的最佳数量\r\n                // 调用quote函数，换算amountBDesired 对应的A代币为多少\r\n                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);\r\n                assert(amountAOptimal <= amountADesired);\r\n                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');\r\n                // 返回希望存入的B，和最佳的A数量\r\n                (amountA, amountB) = (amountAOptimal, amountBDesired);\r\n            }\r\n        }\r\n    }\r\n    function addLiquidity(\r\n        address tokenA, // 代币A\r\n        address tokenB, // 代币B\r\n        uint amountADesired, // 希望存入的代币A数量\r\n        uint amountBDesired, // 希望存入的代币B数量\r\n        uint amountAMin, // 用户可接受的最小成交代币A数量\r\n        uint amountBMin, // 用户可接受的最小成交代币B数量\r\n        address to, // 流动性代币接收地址\r\n        uint deadline // 该笔交易的有效时间，如果超过该时间还没得到交易处理就直接失效不进行交易了\r\n    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {\r\n        // 调用_addLiquidity函数，返回 代币A和代币B 的最佳汇率\r\n        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);\r\n        // 获取交易对地址\r\n        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\r\n        // 调用tokenA的`transferFrom`函数，实现msg.sender向交易对pair转入amountA代币\r\n        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);\r\n        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);\r\n        // 获取存入代币后uniswapv2中的流动性\r\n        liquidity = IUniswapV2Pair(pair).mint(to);\r\n    }\r\n\r\n    /** 添加流动性 */\r\n    function addLiquidityETH(\r\n        address token,\r\n        uint amountTokenDesired,\r\n        uint amountTokenMin,\r\n        uint amountETHMin,\r\n        address to,\r\n        uint deadline\r\n    ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {\r\n        // 调用_addLiquidity函数，返回 TOken和ETH的最佳汇率\r\n        (amountToken, amountETH) = _addLiquidity(\r\n            token,\r\n            WETH,\r\n            amountTokenDesired,\r\n            msg.value,\r\n            amountTokenMin,\r\n            amountETHMin\r\n        );\r\n        // 获取交易对地址\r\n        address pair = UniswapV2Library.pairFor(factory, token, WETH);\r\n        // 调用token的`transferFrom`函数，实现msg.sender向交易对pair转入amountToken代币\r\n        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);\r\n        // 先把amountETH数量的ETH存入本合约\r\n        IWETH(WETH).deposit{value: amountETH}();\r\n        // 再将amount数量的WETH转入交易对中\r\n        assert(IWETH(WETH).transfer(pair, amountETH));\r\n        // 获取存入代币后uniswapv2中的流动性\r\n        liquidity = IUniswapV2Pair(pair).mint(to);\r\n        // refund dust eth, if any\r\n        // 如果还有剩余的ETH，将退还\r\n        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);\r\n    }\r\n\r\n    // **** REMOVE LIQUIDITY ****\r\n    // 移除流动性\r\n    function removeLiquidity(\r\n        address tokenA,\r\n        address tokenB,\r\n        uint liquidity,\r\n        uint amountAMin,\r\n        uint amountBMin,\r\n        address to,\r\n        uint deadline\r\n    ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {\r\n        // 获取交易对地址\r\n        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\r\n        // 将流动性代币从用户划转到 pair 合约\r\n        IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair\r\n        // 收到的流动性代币占全部代币比例\r\n        (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);\r\n        (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);\r\n        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);\r\n        // 如果低于用户设定的最低预期（amountAMin/amountBMin），则回滚交易\r\n        require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');\r\n        require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');\r\n    }\r\n\r\n    function removeLiquidityETH(\r\n        address token, //待移除流动性的代币地址\r\n        uint liquidity, // 要移除的流动性数量\r\n        uint amountTokenMin, // 用户愿意接受的最小代币数量，如果实际返回的代币数量小于该值，则函数会抛出异常\r\n        uint amountETHMin, // 用户愿意接受的最小ETH数量，如果实际返回的ETH数量小于该值，则函数会抛出异常\r\n        address to, // 代币和ETH将被发送到的目标地址\r\n        uint deadline // 操作的截止时间，必须在该时间之前完成操作，否则操作将被视为无效\r\n    ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {\r\n        // 调用 removeLiquidity，得到退还的 Token 和 ETH\r\n        // removeLiquidity中执行了 `burn`但是是将token和ETH转移到了addrss(this),说明还可以执行 safeTransfer 和 withdraw\r\n        (amountToken, amountETH) = removeLiquidity(\r\n            token,\r\n            WETH,\r\n            liquidity,\r\n            amountTokenMin,\r\n            amountETHMin,\r\n            address(this),\r\n            deadline\r\n        );\r\n        // 向to转账(token)\r\n        TransferHelper.safeTransfer(token, to, amountToken);\r\n        // 将weth取出来，暂时存在addrss(this)\r\n        IWETH(WETH).withdraw(amountETH);\r\n        // 将address(this)中的WETH转到指定地址to\r\n        TransferHelper.safeTransferETH(to, amountETH);\r\n    }\r\n\r\n    // 其实就是在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作\r\n    function removeLiquidityWithPermit(\r\n        address tokenA,\r\n        address tokenB,\r\n        uint liquidity,\r\n        uint amountAMin,\r\n        uint amountBMin,\r\n        address to,\r\n        uint deadline,\r\n        bool approveMax, uint8 v, bytes32 r, bytes32 s\r\n    ) external virtual override returns (uint amountA, uint amountB) {\r\n        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);\r\n        uint value = approveMax ? uint(-1) : liquidity;\r\n        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\r\n        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);\r\n    }\r\n    \r\n    // 在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作\r\n    function removeLiquidityETHWithPermit(\r\n        address token,\r\n        uint liquidity,\r\n        uint amountTokenMin,\r\n        uint amountETHMin,\r\n        address to,\r\n        uint deadline,\r\n        bool approveMax, uint8 v, bytes32 r, bytes32 s\r\n    ) external virtual override returns (uint amountToken, uint amountETH) {\r\n        address pair = UniswapV2Library.pairFor(factory, token, WETH);\r\n        uint value = approveMax ? uint(-1) : liquidity;\r\n        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\r\n        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);\r\n    }\r\n\r\n    // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****\r\n    /** \r\n        返回值没有 amountToken；\r\n        调用 removeLiquidity 后也没有 amountToken 值返回\r\n        进行 safeTransfer 时传值直接读取当前地址的 token 余额。\r\n     */\r\n    function removeLiquidityETHSupportingFeeOnTransferTokens(\r\n        address token,\r\n        uint liquidity,\r\n        uint amountTokenMin,\r\n        uint amountETHMin,\r\n        address to,\r\n        uint deadline\r\n    ) public virtual override ensure(deadline) returns (uint amountETH) {\r\n        (, amountETH) = removeLiquidity(\r\n            token,\r\n            WETH,\r\n            liquidity,\r\n            amountTokenMin,\r\n            amountETHMin,\r\n            address(this),\r\n            deadline\r\n        );\r\n        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));\r\n        IWETH(WETH).withdraw(amountETH);\r\n        TransferHelper.safeTransferETH(to, amountETH);\r\n    }\r\n\r\n    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\r\n        address token,\r\n        uint liquidity,\r\n        uint amountTokenMin,\r\n        uint amountETHMin,\r\n        address to,\r\n        uint deadline,\r\n        bool approveMax, uint8 v, bytes32 r, bytes32 s\r\n    ) external virtual override returns (uint amountETH) {\r\n        address pair = UniswapV2Library.pairFor(factory, token, WETH);\r\n        uint value = approveMax ? uint(-1) : liquidity;\r\n        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);\r\n        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(\r\n            token, liquidity, amountTokenMin, amountETHMin, to, deadline\r\n        );\r\n    }\r\n\r\n    // **** SWAP ****\r\n    // requires the initial amount to have already been sent to the first pair\r\n    function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {\r\n        /** 历整个兑换路径，并对路径中每两个配对的 token 调用 pair 合约的兑换函数，实现底层的兑换处理 */\r\n        for (uint i; i < path.length - 1; i++) {\r\n            (address input, address output) = (path[i], path[i + 1]);\r\n            (address token0,) = UniswapV2Library.sortTokens(input, output);\r\n            uint amountOut = amounts[i + 1];\r\n            // 如下语句是在判断要兑换的是哪种 货币，比如 A 或者 B \r\n            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));\r\n            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;\r\n            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(\r\n                amount0Out, amount1Out, to, new bytes(0)\r\n            );\r\n        }\r\n    }\r\n\r\n    /** 用 ERC20 兑换 ERC20，但支付的数量是指定的，而兑换回的数量则是未确定的 */\r\n    function swapExactTokensForTokens(\r\n        uint amountIn,\r\n        uint amountOutMin,\r\n        address[] calldata path,\r\n        address to,\r\n        uint deadline\r\n    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {\r\n        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);\r\n        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        // 将支付的代币转到 pair 合约\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\r\n        );\r\n        _swap(amounts, path, to);\r\n    }\r\n    /** 用 ERC20 兑换 ERC20，与上一个函数不同，指定的是兑换回的数量 */\r\n    function swapTokensForExactTokens(\r\n        uint amountOut,\r\n        uint amountInMax,\r\n        address[] calldata path,\r\n        address to,\r\n        uint deadline\r\n    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {\r\n        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\r\n        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\r\n        );\r\n        _swap(amounts, path, to);\r\n    }\r\n\r\n    /** 指定 ETH 数量兑换 ERC20 */\r\n    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)\r\n        external\r\n        virtual\r\n        override\r\n        payable\r\n        ensure(deadline)\r\n        returns (uint[] memory amounts)\r\n    {\r\n        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);\r\n        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        IWETH(WETH).deposit{value: amounts[0]}();\r\n        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));\r\n        _swap(amounts, path, to);\r\n    }\r\n    /** 用 ERC20 兑换成指定数量的 ETH */\r\n    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)\r\n        external\r\n        virtual\r\n        override\r\n        ensure(deadline)\r\n        returns (uint[] memory amounts)\r\n    {\r\n        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\r\n        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\r\n        );\r\n        _swap(amounts, path, address(this));\r\n        IWETH(WETH).withdraw(amounts[amounts.length - 1]);\r\n        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);\r\n    }\r\n    /** 用指定数量的 ERC20 兑换 ETH */\r\n    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)\r\n        external\r\n        virtual\r\n        override\r\n        ensure(deadline)\r\n        returns (uint[] memory amounts)\r\n    {\r\n        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);\r\n        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]\r\n        );\r\n        _swap(amounts, path, address(this));\r\n        IWETH(WETH).withdraw(amounts[amounts.length - 1]);\r\n        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);\r\n    }\r\n    /** 用 ETH 兑换指定数量的 ERC20 */\r\n    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)\r\n        external\r\n        virtual\r\n        override\r\n        payable\r\n        ensure(deadline)\r\n        returns (uint[] memory amounts)\r\n    {\r\n        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);\r\n        require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');\r\n        IWETH(WETH).deposit{value: amounts[0]}();\r\n        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));\r\n        _swap(amounts, path, to);\r\n        // refund dust eth, if any\r\n        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);\r\n    }\r\n\r\n    // **** SWAP (supporting fee-on-transfer tokens) ****\r\n    // requires the initial amount to have already been sent to the first pair\r\n    \r\n    function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {\r\n        for (uint i; i < path.length - 1; i++) {\r\n            (address input, address output) = (path[i], path[i + 1]);\r\n            (address token0,) = UniswapV2Library.sortTokens(input, output);\r\n            IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));\r\n            uint amountInput;\r\n            uint amountOutput;\r\n            { // scope to avoid stack too deep errors\r\n            (uint reserve0, uint reserve1,) = pair.getReserves();\r\n            (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);\r\n            amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);\r\n            amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);\r\n            }\r\n            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));\r\n            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;\r\n            pair.swap(amount0Out, amount1Out, to, new bytes(0));\r\n        }\r\n    }\r\n\r\n    /** 指定数量的 ERC20 兑换 ERC20，支持转账时扣费 */\r\n    function swapExactTokensForTokensSupportingFeeOnTransferTokens(\r\n        uint amountIn,\r\n        uint amountOutMin,\r\n        address[] calldata path,\r\n        address to,\r\n        uint deadline\r\n    ) external virtual override ensure(deadline) {\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn\r\n        );\r\n        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);\r\n        _swapSupportingFeeOnTransferTokens(path, to);\r\n        require(\r\n            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,\r\n            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'\r\n        );\r\n    }\r\n    /** 指定数量的 ETH 兑换 ERC20，支持转账时扣费 */\r\n    function swapExactETHForTokensSupportingFeeOnTransferTokens(\r\n        uint amountOutMin,\r\n        address[] calldata path,\r\n        address to,\r\n        uint deadline\r\n    )\r\n        external\r\n        virtual\r\n        override\r\n        payable\r\n        ensure(deadline)\r\n    {\r\n        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        uint amountIn = msg.value;\r\n        IWETH(WETH).deposit{value: amountIn}();\r\n        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));\r\n        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);\r\n        _swapSupportingFeeOnTransferTokens(path, to);\r\n        require(\r\n            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,\r\n            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'\r\n        );\r\n    }\r\n     /** 指定数量的 ETH 兑换 ERC20，支持转账时扣费 */\r\n    function swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n        uint amountIn,\r\n        uint amountOutMin,\r\n        address[] calldata path,\r\n        address to,\r\n        uint deadline\r\n    )\r\n        external\r\n        virtual\r\n        override\r\n        ensure(deadline)\r\n    {\r\n        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');\r\n        TransferHelper.safeTransferFrom(\r\n            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn\r\n        );\r\n        _swapSupportingFeeOnTransferTokens(path, address(this));\r\n        uint amountOut = IERC20(WETH).balanceOf(address(this));\r\n        require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        IWETH(WETH).withdraw(amountOut);\r\n        TransferHelper.safeTransferETH(to, amountOut);\r\n    }\r\n\r\n    // **** LIBRARY FUNCTIONS ****\r\n    function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {\r\n        return UniswapV2Library.quote(amountA, reserveA, reserveB);\r\n    }\r\n\r\n    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)\r\n        public\r\n        pure\r\n        virtual\r\n        override\r\n        returns (uint amountOut)\r\n    {\r\n        return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);\r\n    }\r\n\r\n    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)\r\n        public\r\n        pure\r\n        virtual\r\n        override\r\n        returns (uint amountIn)\r\n    {\r\n        return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);\r\n    }\r\n\r\n    function getAmountsOut(uint amountIn, address[] memory path)\r\n        public\r\n        view\r\n        virtual\r\n        override\r\n        returns (uint[] memory amounts)\r\n    {\r\n        return UniswapV2Library.getAmountsOut(factory, amountIn, path);\r\n    }\r\n\r\n    function getAmountsIn(uint amountOut, address[] memory path)\r\n        public\r\n        view\r\n        virtual\r\n        override\r\n        returns (uint[] memory amounts)\r\n    {\r\n        return UniswapV2Library.getAmountsIn(factory, amountOut, path);\r\n    }\r\n}\r\n\r\n```\r\n\r\n> **解读 `addLiquidity`中的amountAMin 和 amountBMin **\r\n>\r\n> 该值一般是由前端根据预期值和滑点值计算得出的。比如，预期值 amountADesired 为 1000，设置的滑点为 0.5%，那就可以计算得出可接受的最小值 amountAMin 为 1000 * (1 - 0.5%) = 995。\r\n>\r\n> **`addLiquidityETH`**\r\n>\r\n> addLiquidityETH 则支付的其中一个 token 则是 ETH，而不是 ERC20 代币。来看看其代码实现：\r\n>\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/QptBdvsa6624cb51b5784.png)\r\n>\r\n> 可看到，入参不再是两个 token 地址，而只有一个 token 地址，因为另一个是以太坊主币 ETH。预期支付的 ETH 金额也是直接从 msg.value 读取的，所以入参里也不需要 ETH 的 Desired 参数。但是会定义 amountETHMin 表示愿意接受成交的 ETH 最小额。\r\n>\r\n> 实现逻辑上，请注意，调用 _addLiquidity 时传入的第二个参数是 WETH。其实，addLiquidityETH 实际上也是将 ETH 转为 WETH 进行处理的。可以看到代码中还有这么一行：\r\n>\r\n> ```solidity\r\n> IWETH(WETH).deposit{value: amountETH}();\r\n> ```\r\n>\r\n> 这就是将用户转入的 ETH 转成了 WETH。\r\n>\r\n> 而最后一行代码则会判断，如果一开始支付的 msg.value 大于实际需要支付的金额，多余的部分将返还给用户\r\n\r\n\r\n## Library\r\n\r\n### 1. UniswapV2Library\r\n\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/ZKtyRwAL6624cb7961173.png)\r\n\r\n**代码解读如下：**\r\n\r\n```solidity\r\npragma solidity >=0.5.0;\r\n\r\nimport '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';\r\n\r\nimport \"./SafeMath.sol\";\r\n\r\nlibrary UniswapV2Library {\r\n    using SafeMath for uint;\r\n\r\n    // returns sorted token addresses, used to handle return values from pairs sorted in this order\r\n    // 给 tokenA和tokenB排序，按字面值排序\r\n    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {\r\n        require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');\r\n        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);\r\n        require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');\r\n    }\r\n\r\n    // calculates the CREATE2 address for a pair without making any external calls\r\n    // 输入工厂地址和两个代币地址，计算这两个代币的交易对地址\r\n    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {\r\n        // 先对这两个代币地址进行排序\r\n        (address token0, address token1) = sortTokens(tokenA, tokenB);\r\n        // 采用create2的方式计算地址\r\n        pair = address(uint(keccak256(abi.encodePacked(\r\n                hex'ff',\r\n                factory,\r\n                keccak256(abi.encodePacked(token0, token1)),\r\n                // UniswapV2Pair 合约的 creationCode 的哈希值\r\n                hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash\r\n            ))));\r\n    }\r\n\r\n    // fetches and sorts the reserves for a pair\r\n    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {\r\n        // 先让 tokenA 和 tokenB 从小到大排列\r\n        (address token0,) = sortTokens(tokenA, tokenB);\r\n\r\n        // 根据 `pairFor(factory, tokenA, tokenB)`算出一个新的地址\r\n        // .getReserves() 获取 该新地址的 reserve0，reserve1\r\n        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();\r\n\r\n        // 如果交换过顺序就交换输出，简单来说及时为了对应输入的形参\r\n        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);\r\n    }\r\n\r\n    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset\r\n    /**\r\n        数量为amountA的代币A，按照合约中两种代币余额比例，换算成另一个代币B。此时不考虑手续费，因为仅是计价单位的换算\r\n        【根据给定的两个 token 的储备量和其中一个 token 数量，计算得到另一个 token 等值的数值】\r\n     */\r\n    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {\r\n        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');\r\n        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\r\n        amountB = amountA.mul(reserveB) / reserveA;\r\n    }\r\n\r\n    \r\n    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset\r\n    /**\r\n        该方法计算：输入一定数量（amountIn）代币A，根据池子中代币余额，能得到多少数量（amountOut）代币B。\r\n        amountIn指输入的代币A，reserveIn 指代币A的存储量，reserveOut指代币B的存储量\r\n\r\n        【根据给定的两个 token 的储备量和输入的 token 数量，计算得到输出的 token 数量，该计算会扣减掉 0.3% 的手续费】\r\n     */\r\n    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {\r\n        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');\r\n        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\r\n        uint amountInWithFee = amountIn.mul(997);\r\n        uint numerator = amountInWithFee.mul(reserveOut);\r\n        uint denominator = reserveIn.mul(1000).add(amountInWithFee);\r\n        amountOut = numerator / denominator;\r\n    }\r\n\r\n    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset\r\n    /**\r\n        该方法计算当希望获得一定数量（amountOut）的代币B时，应该输入多少数量（amoutnIn）的代币A。\r\n        amountOut指要得到的代币B，reserveIn 指代币A的存储量，reserveOut指代币B的存储量\r\n        【根据给定的两个 token 的储备量和输出的 token 数量，计算得到输入的 token 数量，该计算会扣减掉 0.3% 的手续费】\r\n     */\r\n    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {\r\n        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');\r\n        uint numerator = reserveIn.mul(amountOut).mul(1000);\r\n        uint denominator = reserveOut.sub(amountOut).mul(997);\r\n        amountIn = (numerator / denominator).add(1);\r\n    }\r\n\r\n    // performs chained getAmountOut calculations on any number of pairs\r\n    /**\r\n        该方法用于计算在使用多个交易对时，输入一定数量（amountIn）的第一种代币，\r\n        最终能收到多少数量的最后一种代币（amounts）。amounts数组中的第一个元素表示amountIn，\r\n        最后一个元素表示该目标代币对应的数量。该方法实际上是循环调用getAmountIn方法。\r\n        【根据兑换路径和输入数量，计算得到兑换路径中每个交易对的输出数量】\r\n     */\r\n    function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {\r\n        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');\r\n        // 创建一个和path等长的uint数组\r\n        amounts = new uint[](path.length);\r\n        amounts[0] = amountIn;\r\n        for (uint i; i < path.length - 1; i++) {\r\n            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);\r\n            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);\r\n        }\r\n    }\r\n\r\n    // performs chained getAmountIn calculations on any number of pairs\r\n    /**\r\n        与getAmountsOut相对，getAmountsIn用于计算当希望收到一定数量（amountOut）的目标代币，\r\n        应该分别输入多少数量的中间代币。计算方法也是循环调用getAmountIn。\r\n     */\r\n    function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {\r\n        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');\r\n        amounts = new uint[](path.length);\r\n        amounts[amounts.length - 1] = amountOut;\r\n        for (uint i = path.length - 1; i > 0; i--) {\r\n            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);\r\n            amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);\r\n        }\r\n    }\r\n}\r\n\r\n\r\n```\r\n\r\n> *getAmountOut* 的实现：\r\n>\r\n![image.png](https://img.learnblockchain.cn/attachments/2024/04/hnXFAk1H6624cb9279c92.png)\r\n>\r\n> 根据 AMM 的原理，恒定乘积公式「x * y = K」，兑换前后 K 值不变。因此，在不考虑交易手续费的情况下，以下公式会成立：\r\n>\r\n> ```mathematica\r\n> reserveIn * reserveOut = (reserveIn + amountIn) * (reserveOut - amountOut)\r\n> ```\r\n>\r\n> 将公式右边的表达式展开，并推导下，就变成了：\r\n>\r\n> ```mathematica\r\n> reserveIn * reserveOut = reserveIn * reserveOut + amountIn * reserveOut - (reserveIn + amountIn) * amountOut\r\n> ->\r\n> amountIn * reserveOut = (reserveIn + amountIn) * amountOut\r\n> ->\r\n> amountOut = amountIn * reserveOut / (reserveIn + amountIn)\r\n> ```\r\n>\r\n> 而实际上交易时，还需要扣减千分之三的交易手续费，所以实际上：\r\n>\r\n> ```mathematica\r\n> amountIn = amountIn * 997 / 1000\r\n> ```\r\n>\r\n> 代入上面的公式后，最终结果就变成了：\r\n>\r\n> ```mathematic\r\n> amountOut = (amountIn * 997 / 1000) * reserverOut / (reserveIn + amountIn * 997 / 1000)\r\n> ->\r\n> amountOut = amountIn * 997 * reserveOut / 1000 * (reserveIn + amountIn * 997 / 1000)\r\n> ->\r\n> amountOut = amountIn * 997 * reserveOut / (reserveIn * 1000 + amountIn * 997)\r\n> ```\r\n>\r\n> 这即是最后代码实现中的计算公式了。\r\n>\r\n> \r\n>\r\n> **解读`getAmountsOut`**\r\n>\r\n> 根据兑换路径和输入数量，计算得到兑换路径中每个交易对的输出数量。\r\n>\r\n> 举例：\r\n>\r\n> 假如一个交易地址数组 path[A,B,C,D]\t，其中 \r\n>\r\n> pair(A,B)中的tokenA=6，tokenB=9； pair(B,C)中的tokenA=10，tokenB=8； pair(C,D)中的tokenA=19，tokenB=84\r\n>\r\n> 则 amounts[0] = IN_tokenA,  \r\n>\r\n> pair(A,B) : amount[1] = getAmountOut(IN_tokenA, reserveA, reserveB),\r\n>\r\n> pair(B,C): amount[2] = getAmountOut(IN_tokenA, reserveA, reserveB),\r\n>\r\n>  pair(C,D): amount[3] = getAmountOut(IN_tokenA, reserveA, reserveB).\r\n>\r\n> > 该函数会计算 path 中每一个中间资产和最终资产的数量，比如 path 为 [A,B,C]，则会先将 A 兑换成 B，再将 B 兑换成 C。返回值则是一个数组，第一个元素是 A 的数量，即 amountIn，而第二个元素则是兑换到的代币 B 的数量，最后一个元素则是最终要兑换得到的代币 C 的数量。\r\n> >\r\n> > 从代码中还可看到，每一次兑换其实都调用了 *getAmountOut* 函数，这也意味着每一次中间兑换都会扣减千分之三的交易手续费。那如果兑换两次，实际支付假设为 1000，那最终实际兑换得到的价值只剩下：\r\n> >\r\n> > ```mathematica\r\n> > 1000 * (1 - 0.003) * (1 - 0.003) = 994.009\r\n> > ```\r\n> >\r\n> > 即实际支付的交易手续费将近千分之六了。兑换路径越长，实际扣减的交易手续费会更多，所以兑换路径一般不宜过长。\r\n>\r\n> **`getAmountsIn`同理**\r\n\r\n**最后，码字不易，点个赞呗~🤪**\r\n\r\n## 参考链接\r\n\r\n[uniswap v2白皮书 链接1](https://mirror.xyz/adshao.eth/VY6aLzdjwXGif9O1C7UMuYFmivC4q5jDQqQUho6tLWY)\r\n\r\n[uniswap v2代码 链接2](https://mirror.xyz/adshao.eth/qmzSfrOB8s6_-s1AsflYNqEkTynShdpBE0EliqjGC1U)\r\n\r\n[uiniswap v2代码core代码解读 链接3](https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/#uniswapv2pair)\r\n\r\n[登链社区](https://learnblockchain.cn/article/3047)"},"author":{"user":"https://learnblockchain.cn/people/13480","address":"0x994dD9bA9611DEEE007EC1189FFb650943094240"},"history":null,"timestamp":1713694453,"version":1}