原文发布在 https://github.com/33357/smartcontract-apps这是一个面向中文社区,分析市面上智能合约应用的架构与实现的仓库。欢迎关注开源知识项目!
交易
- 内部函数(仅供合约内部调用)
- _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
) internal virtual {
...
}
函数 _swap 实现了由多重交易组成的交易集合。path 数组里定义了执行代币交易的顺序,amounts 数组里定义了每次交换获得代币的期望数量,_to 则是最后获得代币发送到的地址。 -
实现分析 ...
{
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 时,需要排列 amount0Out 、amount1Out 的顺序,因此需要计算 input 、output 中谁是 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,
uint deadline
) external virtual override ensure(deadline) returns (
uint[] memory amounts
) {
...
}
函数 swapExactTokensForTokens 实现了用户使用数量精确的 tokenA 交易数量不精确的 tokenB 的流程。用户使用确定的 amountIn 数量的 tokenA ,交易获得 tokenB 的数量不会小于 amountOutMin ,但具体 tokenB 的数量只有交易完成之后才能知道。这同样是由于区块链上交易不是实时的,实际交易和预期交易相比会有一定的偏移。 -
实现分析 ...
ensure(deadline)
{
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);
}
-
总结 可以看到,由于区块链上的实际交易和预期交易有偏差是常见的事情,因此在设计链上交易的时候逻辑会比较复杂,条件选择会有很多。 - 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,
uint deadline
) external virtual override ensure(deadline) returns (
uint[] memory amounts
){
...
}
函数 swapTokensForExactTokens 实现了用户使用数量不精确的 tokenA 交易数量精确的 tokenB 的流程。用户会使用数量不大于 amountInMax 数量的 tokenA,交易获得 amountOut 数量的 tokenB。 -
实现分析 ...
ensure(deadline)
{
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 完全是函数 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,
uint deadline
) external virtual override payable ensure(deadline) returns (
uint[] memory amounts
){
...
}
函数 swapExactETHForTokens 和函数 swapExactTokensForTokens 的逻辑几乎一样,只是把支付精确数量的 token 换成了支付精确数量的 ETH。因此多了一些和 ETH 相关的额外操作。 -
实现分析 ...
ensure(deadline)
{
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);
}
-
总结 此函数一般用于出售确定数量的 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,
uint deadline
)external virtual override ensure(deadline) returns (
uint[] memory amounts
){
...
}
函数 swapTokensForExactETH 和 函数 swapExactETHForTokens 相比,仅交换了一下代币的交易顺序,执行逻辑还是差不多的。 -
实现分析 ...
ensure(deadline)
{
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]);
}
-
总结 此函数一般用于购买确定数量的 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,
uint deadline
) external virtual override ensure(deadline) returns (
uint[] memory amounts
){
...
}
函数 swapExactTokensForETH 和 函数 swapTokensForExactETH 相比,是更换了输入精确数量代币的顺序。 -
实现分析 ...
ensure(deadline)
{
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]);
}
-
总结 此函数一般用于出售确定数量代币,获得不确定数量的 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,
uint deadline
) external virtual override payable ensure(deadline) returns (
uint[] memory amounts
){
...
}
函数 swapETHForExactTokens 和 函数 swapExactTokensForETH 相比,更换了代币交易的顺序。 -
实现分析 ...
ensure(deadline)
{
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]);
}
-
总结 此函数一般用于购买确定数量代币,支付不确定数量的 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
) internal virtual {
...
}
函数 _swapSupportingFeeOnTransferTokens 相比函数 _swap 为了支持 path 中有交易后可变数量的代币,不需要输入 amounts ,但需要额外做一些操作。 -
实现分析 ...
{
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));
}
}
-
总结 可以看到,因为没有 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,
uint deadline
) external virtual override ensure(deadline) {
...
}
函数 swapExactTokensForTokensSupportingFeeOnTransferTokens 相比函数 swapExactTokensForTokens ,少了 amounts ,因为交易后可变数量的代币不能做amounts 的预测。 -
实现分析 ...
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'
);
}
-
总结 该函数适用于支付确定数量的代币,获得不定数量的代币,且在 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,
uint deadline
) external virtual override payable ensure(deadline)
{
...
}
函数 swapExactETHForTokensSupportingFeeOnTransferTokens 相比函数 swapExactETHForTokens ,同样少了 amounts ,因为交易后可变数量的代币不能做amounts 的预测。 -
实现分析 ...
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'
);
}
-
总结 该函数适用于支付确定数量的 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,
uint deadline
) external virtual override ensure(deadline) {
...
}
函数 swapExactTokensForETHSupportingFeeOnTransferTokens 相比函数 swapExactTokensForETH ,也少了 amounts ,因为交易后可变数量的代币不能做amounts 的预测。 -
实现分析 ...
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);
}
-
总结 该函数适用于支付确定数量的 token,获得不定数量的 WETH,且在 path 路径列表中有交易后数量可变的代币。
|