6

Uniswap-v2 Router合约分析(下)

 2 years ago
source link: https://learnblockchain.cn/article/3589
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Uniswap-v2 Router合约分析(下)

Uniswap-v2 Router合约分析(下)

Uniswap-v2 Router合约分析(下)

原文发布在 https://github.com/33357/smartcontract-apps这是一个面向中文社区,分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目!

本文接上文Uniswap-v2 Router合约分析(上)

  • 内部函数(仅供合约内部调用)
    • _swap
      • 代码速浏览

        function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
            for (uint i; i < path.length - 1; i++) {
                (address input, address output) = (path[i], path[i + 1]);
                (address token0,) = UniswapV2Library.sortTokens(input, output);
                uint amountOut = amounts[i + 1];
                (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
                address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
                IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                    amount0Out, amount1Out, to, new bytes(0)
                );
            }
        }
        
      • 函数 _swap 的入参有3个,出参有0个,对应的解释如下:

        function _swap(
            uint[] memory amounts, // 交易期望数量列表
            address[] memory path, // 交易路径列表
            address _to // 交易获得的 token 发送到的地址
        ) internal virtual {
            ...
        }
        

        函数 _swap 实现了由多重交易组成的交易集合。path 数组里定义了执行代币交易的顺序,amounts 数组里定义了每次交换获得代币的期望数量,_to 则是最后获得代币发送到的地址。

      • ...
        {
            // 循环交易路径列表
            for (uint i; i < path.length - 1; i++) {
                // 从 path 中取出 input 和 output
                (address input, address output) = (path[i], path[i + 1]);
                // 从 input 和 output 中算出谁是 token0
                (address token0,) = UniswapV2Library.sortTokens(input, output);
                // 期望交易获得的代币数量
                uint amountOut = amounts[i + 1];
                // 如果 input == token0,那么 amount0Out 就是0,amount1Out 就是 amountOut;反之则相反
                (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
                // 如果这是最后的一笔交易,那么 to 地址就是 _to,否则 to 地址是下一笔交易的流动池地址
                address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
                // 执行 input 和 output 的交易
                IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                    amount0Out, amount1Out, to, new bytes(0)
                );
            }
        }
        
      • 由于执行 swap 时,需要排列 amount0Outamount1Out 的顺序,因此需要计算 inputoutput 中谁是 token0

  • 外部函数(仅供合约外部调用)
    • swapExactTokensForTokens
      • 代码速浏览

        function swapExactTokensForTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
            amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            _swap(amounts, path, to);
        }
        
      • 函数swapExactTokensForTokens的入参有5个,出参有1个,对应的解释如下:

        function swapExactTokensForTokens(
            uint amountIn,// 交易支付代币数量
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
        ) {
            ...
        }
        

        函数 swapExactTokensForTokens 实现了用户使用数量精确的 tokenA 交易数量不精确的 tokenB 的流程。用户使用确定的 amountIn 数量的 tokenA ,交易获得 tokenB 的数量不会小于 amountOutMin,但具体 tokenB 的数量只有交易完成之后才能知道。这同样是由于区块链上交易不是实时的,实际交易和预期交易相比会有一定的偏移。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 获取 path 列表下,支付 amountIn 数量的 path[0] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
            // 如果最终获得的代币数量小于 amountOutMin,则交易失败
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            // 按 path 列表执行交易集合
            _swap(amounts, path, to);
        }
        
      • 可以看到,由于区块链上的实际交易和预期交易有偏差是常见的事情,因此在设计链上交易的时候逻辑会比较复杂,条件选择会有很多。

    • swapTokensForExactTokens
      • 代码速浏览

        function swapTokensForExactTokens(
            uint amountOut,
            uint amountInMax,
            address[] calldata path,
            address to,
            uint deadline
        ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            _swap(amounts, path, to);
        }
        
      • 函数swapTokensForExactTokens的入参有5个,出参有1个,对应的解释如下:

        function swapTokensForExactTokens(
            uint amountOut, // 交易获得的代币数量
            uint amountInMax, // 交易支付代币的最多数量
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
            ){
            ...
        }
        

        函数 swapTokensForExactTokens 实现了用户使用数量不精确的 tokenA 交易数量精确的 tokenB 的流程。用户会使用数量不大于 amountInMax 数量的 tokenA,交易获得 amountOut 数量的 tokenB。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 获取 path 列表下,获得 amountIn 数量的 path[path.length-1] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            // 如果 path[0] 代币数量大于 amountInMax,则交易失败
            require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            // 按 path 列表执行交易集合
            _swap(amounts, path, to);
        }
        
      • 函数 swapTokensForExactTokens 完全是函数 swapExactTokensForTokens 的相反操作。一般来说,swapExactTokensForTokens 用于出售确定数量的代币,swapTokensForExactTokens 用于购买确定数量的代币。

    • swapExactETHForTokens
      • 代码速浏览

        function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
            external
            virtual
            override
            payable
            ensure(deadline)
            returns (uint[] memory amounts)
        {
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            IWETH(WETH).deposit{value: amounts[0]}();
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
            _swap(amounts, path, to);
        }
        
      • 函数swapExactETHForTokens的入参有4个,出参有1个,对应的解释如下:

        function swapExactETHForTokens(
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override payable ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
        ){
            ...
        }
        

        函数 swapExactETHForTokens 和函数 swapExactTokensForTokens 的逻辑几乎一样,只是把支付精确数量的 token 换成了支付精确数量的 ETH。因此多了一些和 ETH 相关的额外操作。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 检查 path[0] 是否为 WETH 地址
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 获取 path 列表下,支付 amountIn 数量的 path[0] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
            // 如果最终获得的代币数量小于 amountOutMin,则交易失败
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            // 把用户支付的 ETH 换成 WETH
            IWETH(WETH).deposit{value: amounts[0]}();
            // 将 amounts[0] 数量的 WETH 代币从 Router 中转移到 path[0], path[1] 的流动池
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
            // 按 path 列表执行交易集合
            _swap(amounts, path, to);
        }
        
      • 此函数一般用于出售确定数量的 ETH,获得不确定数量代币。

    • swapTokensForExactETH
      • 代码速浏览

        function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
            external
            virtual
            override
            ensure(deadline)
            returns (uint[] memory amounts)
        {
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            _swap(amounts, path, address(this));
            IWETH(WETH).withdraw(amounts[amounts.length - 1]);
            TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
        }
        
      • 函数 swapTokensForExactETH 的入参有5个,出参有1个,对应的解释如下:

        function swapTokensForExactETH(
            uint amountOut, // 交易获得的代币数量
            uint amountInMax, // 交易支付代币的最多数量
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        )external virtual override ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
        ){
            ...
        }
        

        函数 swapTokensForExactETH 和 函数 swapExactETHForTokens 相比,仅交换了一下代币的交易顺序,执行逻辑还是差不多的。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 检查 path[path.length - 1] 是否为 WETH 地址
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 获取 path 列表下,获得 amountOut 数量的 path[path.length-1] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            // 如果最终获得的代币数量小于 amountOutMin,则交易失败
            require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            // 按 path 列表执行交易集合
            _swap(amounts, path, address(this));
            // 将 WETH 换成 ETH
            IWETH(WETH).withdraw(amounts[amounts.length - 1]);  
            // 把 ETH 发送给 to 地址
            TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
        }
        
      • 此函数一般用于购买确定数量的 ETH,用不定数量的代币交换。

    • swapExactTokensForETH
      • 代码速浏览

        function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
            external
            virtual
            override
            ensure(deadline)
            returns (uint[] memory amounts)
        {
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            _swap(amounts, path, address(this));
            IWETH(WETH).withdraw(amounts[amounts.length - 1]);
            TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
        }
        
      • 函数swapExactTokensForETH的入参有5个,出参有1个,对应的解释如下:

        function swapExactTokensForETH(
            uint amountIn,// 交易支付代币数量
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
        ){
            ...
        }
        

        函数 swapExactTokensForETH 和 函数 swapTokensForExactETH 相比,是更换了输入精确数量代币的顺序。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 检查 path[path.length - 1] 是否为 WETH 地址
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 获取 path 列表下,支付 amountIn 数量的 path[0] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
            // 如果最终获得的代币数量小于 amountOutMin,则交易失败
            require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
            );
            // 按 path 列表执行交易集合
            _swap(amounts, path, address(this));
            // 将 WETH 换成 ETH
            IWETH(WETH).withdraw(amounts[amounts.length - 1]);
            // 把 ETH 发送给 to 地址
            TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
        }
        
      • 此函数一般用于出售确定数量代币,获得不确定数量的 ETH。

    • swapETHForExactTokens
      • 代码速浏览

        function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
            external
            virtual
            override
            payable
            ensure(deadline)
            returns (uint[] memory amounts)
        {
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            IWETH(WETH).deposit{value: amounts[0]}();
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
            _swap(amounts, path, to);
            if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
        }
        
      • 函数swapETHForExactTokens的入参有4个,出参有1个,对应的解释如下:

        function swapETHForExactTokens(
            uint amountOut, // 交易获得的代币数量
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override payable ensure(deadline) returns (
            uint[] memory amounts // 交易期望数量列表
        ){
            ...
        }
        

        函数 swapETHForExactTokens 和 函数 swapExactTokensForETH 相比,更换了代币交易的顺序。

      • ... 
        // 检查交易是否过期
        ensure(deadline)
        {
            // 检查 path[0] 是否为 WETH 地址
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 获取 path 列表下,获得 amountOut 数量的 path[path.length-1] 代币,各个代币交易的预期数量
            amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
            // 如果 ETH 数量小于 amounts[0],交易失败
            require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
            // 将 WETH 换成 ETH
            IWETH(WETH).deposit{value: amounts[0]}();
            // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
            // 按 path 列表执行交易集合
            _swap(amounts, path, to);
            // 如果 ETH 数量大于 amounts[0],返还多余的 ETH
            if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
        }
        
      • 此函数一般用于购买确定数量代币,支付不确定数量的 ETH。

交易(支持代付GAS代币)

  • 内部函数(仅供合约内部调用)
    • _swapSupportingFeeOnTransferTokens
      • 代码速浏览

        function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
            for (uint i; i < path.length - 1; i++) {
                (address input, address output) = (path[i], path[i + 1]);
                (address token0,) = UniswapV2Library.sortTokens(input, output);
                IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
                uint amountInput;
                uint amountOutput;
                {
                    (uint reserve0, uint reserve1,) = pair.getReserves();
                    (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
                    amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
                    amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
                }
                (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
                address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
                pair.swap(amount0Out, amount1Out, to, new bytes(0));
            }
        }
        
      • 函数 swapETHForExactTokens 的入参有2个,出参有0个,对应的解释如下:

        function _swapSupportingFeeOnTransferTokens(
            address[] memory path, // 交易路径列表
            address _to // 交易获得的 token 发送到的地址
        ) internal virtual {
            ...
        }
        

        函数 _swapSupportingFeeOnTransferTokens 相比函数 _swap 为了支持 path 中有交易后可变数量的代币,不需要输入 amounts,但需要额外做一些操作。

      • ... 
        {
            // 循环交易路径列表
            for (uint i; i < path.length - 1; i++) {
                // 从 path 中取出 input 和 output
                (address input, address output) = (path[i], path[i + 1]);
                // 从 input 和 output 中算出谁是 token0
                (address token0,) = UniswapV2Library.sortTokens(input, output);
                // 获得 input, output 的流动池
                IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
                uint amountInput;
                uint amountOutput;
                {
                    // 获取流动池库存 reserve0, reserve1
                    (uint reserve0, uint reserve1,) = pair.getReserves();
                    // 如果 input == token0,那么 (reserveInput,reserveOutput) 就是 (reserve0, reserve1);反之则相反
                    (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
                    // amountInput 等于流动池余额减去 reserveInput
                    amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
                    // 获取 amountOutput
                    amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
                }
                // 如果 input == token0,那么 amount0Out 就是0,amount1Out 就是 amountOut;反之则相反
                (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
                // 如果这是最后的一笔交易,那么 to 地址就是 _to,否则 to 地址是下一笔交易的流动池地址
                address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
                // 执行 input 和 output 的交易
                pair.swap(amount0Out, amount1Out, to, new bytes(0));
            }
        }
        
      • 可以看到,因为没有 amounts,需要使用流动池余额减去库存来计算amountInput

  • 外部函数(仅供合约外部调用)
    • swapExactTokensForTokensSupportingFeeOnTransferTokens
      • 代码速浏览

        function swapExactTokensForTokensSupportingFeeOnTransferTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        ) external virtual override ensure(deadline) {
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
            );
            uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
            _swapSupportingFeeOnTransferTokens(path, to);
            require(
                IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
                'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
            );
        }
        
      • 函数swapExactTokensForTokensSupportingFeeOnTransferTokens的入参有5个,出参有0个,对应的解释如下:

        function swapExactTokensForTokensSupportingFeeOnTransferTokens(
            uint amountIn,// 交易支付代币数量
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override ensure(deadline) {
            ...
        }
        

        函数 swapExactTokensForTokensSupportingFeeOnTransferTokens 相比函数 swapExactTokensForTokens,少了 amounts,因为交易后可变数量的代币不能做amounts的预测。

      • ... 
        // 检查交易是否过期
        ensure(deadline) 
        {
            // 将 amountIn 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
            );
            // 记录 to 地址 path[path.length - 1] 代币的余额
            uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
            // 按 path 列表执行交易集合
            _swapSupportingFeeOnTransferTokens(path, to);
            // 如果 to 地址获得的代币数量小于 amountOutMin,交易失败
            require(
                IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
                'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
            );
        }
        
      • 该函数适用于支付确定数量的代币,获得不定数量的代币,且在 path 路径列表中有交易后数量可变的代币。

    • swapExactETHForTokensSupportingFeeOnTransferTokens
      • 代码速浏览

        function swapExactETHForTokensSupportingFeeOnTransferTokens(
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        )
            external
            virtual
            override
            payable
            ensure(deadline)
        {
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            uint amountIn = msg.value;
            IWETH(WETH).deposit{value: amountIn}();
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
            uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
            _swapSupportingFeeOnTransferTokens(path, to);
            require(
                IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
                'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
            );
        }
        
      • 函数swapExactETHForTokensSupportingFeeOnTransferTokens的入参有5个,出参有0个,对应的解释如下:

        function swapExactETHForTokensSupportingFeeOnTransferTokens(
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override payable ensure(deadline)
        {
            ...
        }
        

        函数 swapExactETHForTokensSupportingFeeOnTransferTokens 相比函数 swapExactETHForTokens,同样少了 amounts,因为交易后可变数量的代币不能做amounts的预测。

      • ... 
        // 检查交易是否过期
        ensure(deadline) 
        {
            // 检查 path[0] 是否为 WETH
            require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 获取 amountIn
            uint amountIn = msg.value;
            // 把 ETH 换成 WETH
            IWETH(WETH).deposit{value: amountIn}();
            // 将 amountIn 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
            // 记录 to 地址 path[path.length - 1] 代币的余额
            uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
            // 按 path 列表执行交易集合
            _swapSupportingFeeOnTransferTokens(path, to);
            // 如果 to 地址获得的代币数量小于 amountOutMin,交易失败
            require(
                IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
                'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
            );
        }
        
      • 该函数适用于支付确定数量的 ETH,获得不定数量的代币,且在 path 路径列表中有交易后数量可变的代币。

    • swapExactTokensForETHSupportingFeeOnTransferTokens
      • 代码速浏览

        function swapExactTokensForETHSupportingFeeOnTransferTokens(
            uint amountIn,
            uint amountOutMin,
            address[] calldata path,
            address to,
            uint deadline
        )
            external
            virtual
            override
            ensure(deadline)
        {
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
            );
            _swapSupportingFeeOnTransferTokens(path, address(this));
            uint amountOut = IERC20(WETH).balanceOf(address(this));
            require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            IWETH(WETH).withdraw(amountOut);
            TransferHelper.safeTransferETH(to, amountOut);
        }
        
      • 函数swapExactTokensForETHSupportingFeeOnTransferTokens的入参有5个,出参有0个,对应的解释如下:

        function swapExactTokensForETHSupportingFeeOnTransferTokens(
            uint amountIn,// 交易支付代币数量
            uint amountOutMin, // 交易获得代币最小值
            address[] calldata path, // 交易路径列表
            address to, // 交易获得的 token 发送到的地址
            uint deadline // 过期时间
        ) external virtual override ensure(deadline) {
            ...
        }
        

        函数 swapExactTokensForETHSupportingFeeOnTransferTokens 相比函数 swapExactTokensForETH,也少了 amounts,因为交易后可变数量的代币不能做amounts的预测。

      • ... 
        // 检查交易是否过期
        ensure(deadline) 
        {
            // 检查 path[path.length - 1] 是否为 WETH
            require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
            // 将 amountIn 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
            TransferHelper.safeTransferFrom(
                path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
            );
            // 按 path 列表执行交易集合
            _swapSupportingFeeOnTransferTokens(path, address(this));
            // 获取 Router 中的 WETH 余额
            uint amountOut = IERC20(WETH).balanceOf(address(this));
            // amountOut 大于 amountOutMin,否则交易失败
            require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
            // 将 WETH 换成 ETH
            IWETH(WETH).withdraw(amountOut);
            // 将 ETH 发送给 to 地址
            TransferHelper.safeTransferETH(to, amountOut);
        }
        
      • 该函数适用于支付确定数量的 token,获得不定数量的 WETH,且在 path 路径列表中有交易后数量可变的代币。

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2天前
  • 阅读 ( 146 )
  • 学分 ( 4 )
  • 分类:Uniswap

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK