原文发布在 https://github.com/33357/smartcontract-apps这是一个面向中文社区,分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目!
代码速浏览
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
时,需要排列 amount0Out
、amount1Out
的顺序,因此需要计算 input
、output
中谁是 token0
。
代码速浏览
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);
}
总结
可以看到,由于区块链上的实际交易和预期交易有偏差是常见的事情,因此在设计链上交易的时候逻辑会比较复杂,条件选择会有很多。
代码速浏览
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
用于购买确定数量的代币。
代码速浏览
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,获得不确定数量代币。
代码速浏览
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,用不定数量的代币交换。
代码速浏览
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。
代码速浏览
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。
代码速浏览
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
。
代码速浏览
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 路径列表中有交易后数量可变的代币。
代码速浏览
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 路径列表中有交易后数量可变的代币。
代码速浏览
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 路径列表中有交易后数量可变的代币。