Uniswap-v2 Router合约分析(下)

  • 33357
  • 更新于 2022-02-21 19:42
  • 阅读 6736

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 路径列表中有交易后数量可变的代币。

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

0 条评论

请先 登录 后评论
33357
33357
0x1f24...fa59
传播区块链技术,躺赢未来人生