一旦一个 token pair 具有了流动性,任何用户都可以进行兑换(swap)。
兑换过程,是按照 恒定乘积公式 进行的,即 x * y = k
。其中,x 表示其中一种 token 的存量;y 表示另外一种 token 的存量。k 值是一个常量,任何一次交易之后,x 与 y 的乘积,需要始终保持常量 k。
UniswapV2-创建 UniswapV2Pair
UniswapV2-添加流动性
UniswapV2-移除流动性
UniswapV2-兑换(swap)
UniswapV2-公式分析
一、概述
实际上,每次用户在兑换时,都需要提供手续费给 LP,这种机制,对 LP 是一种激励。
我们假定用户输入了 dx 数量的 token0,并希望兑取 token1。如果 Uniswap 中存在 token0 和 token1 的 pair,则其兑换过程会满足:
其中,dy 就是兑换后可以收到的 token1 的数量。
如果,Uniswap 中并没有直接的 token0 和 token1 对,但是可以通过路由接力的方式进行兑换,则这个过程中,每经过一次路由,都需要收取 0.3% 的手续费,这 0.3% 实际上从输入量(本金)上扣除的,类似于“砍头息”。
那么,0.3% 的手续费去哪里了呢?直接进入了 pair 中 token0 的池子里,-造市商移除流动性 的时候,会根据 “占股” 瓜分 token0,平台也可能会参与到瓜分过程中,如果协议费开关打开的话。
如下图所示,如果从 From 无法达到 to,是无法完成兑换的。
假设,我们希望兑换指定数量的 ETH
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
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); }
|
二、计算每次路由的输入和输出(扣除手续费)
这个过程,实际上会就计算好最初的用户输入额度途径每个 pair 后的输入和输出。
在下述代码块中的循环中,我们可以看到,每次路过一个 pair,都会扣除 0.3% 的手续费。所以,如果能够直接兑换,所付手续费是最小的。
1 2 3 4 5 6 7 8 9 10 11 12
| function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) { require(path.length >= 2, 'UniswapV2Library: INVALID_PATH'); amounts = new uint[](path.length); amounts[0] = amountIn; for (uint i; i < path.length - 1; i++) { (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]); amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); } }
|
将输入量 amountIn 先扣除 0.3% 的手续费,再通过恒定乘积公式得到 amountOut.
1 2 3 4 5 6 7 8 9 10 11 12 13
|
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) { require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY'); uint amountInWithFee = amountIn.mul(997); uint numerator = amountInWithFee.mul(reserveOut); uint denominator = reserveIn.mul(1000).add(amountInWithFee); amountOut = numerator / denominator; }
|
三、转账
在第二步骤中,已经计算好了途经每个 pair 的输入和输出数量,这里,将在每个途经的 pair 中,完成转账动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
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,是在 UniswapV2Pair 中完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0; uint balance1; { address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); }
_update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); }
|
Transfer 方法如下:
1 2 3 4 5 6
| bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
function _safeTransfer(address token, address to, uint value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); }
|
四、更新余额
转账完成后,更新 pair 中 token 的存量。更新完成后,则兑换过程顺利结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed; } reserve0 = uint112(balance0); reserve1 = uint112(balance1); blockTimestampLast = blockTimestamp; emit Sync(reserve0, reserve1); }
|
五、小结
无论是添加流动性,还是兑换过程,都是在智能合约内完成的,谓之“去中心化”,用数学构建的 “去中心化”。
兑换导致价值改变,即只要兑换持续,价值波动也将一直存在,起起伏伏,谓之“无常”,市场价值的 “无常”。
参考链接
Router02