UniswapV2 walkthrough
1. Swap结算价格计算
假设一个pair有100 TokenA , 100 TokenB
即假设1 TokenA = 1 TokenB
X,Y代表TokenA,TokenB的数量
任意时刻应满足:
取到大于是因为存在手续费让k上涨

如果用25 TokenA换25 TokenB => (125 TokenA , 75 TokenB) , K’ = 9375

K’ < K , 不满足公式
实际求解(不算fee):

通用公式:
2. 为什么不适用EIP-1167最小代理来克隆pair
A:如果使用最小代理部署,每笔交易delegate call额外消耗2600 Gas , 长期来看部署节省的成本被高频交易抵消
3. Swap 手续费
每次swap流程,用户输入token的0.3%作为手续费fee扣掉,直接进入池子,此时K上涨。
4. FeeOn 协议费
若开启了FeeOn(大多数协议都不开),则在burnormint的时候增发k涨幅1/6的LP Token给feeto地址(一般是项目方)
注:
fee是swap手续费,和协议费不是一个东西,协议费通过mintFee()函数计算mintFee()是增发,不是扣手续费fee的1/6swap不进行mintFee,节省gas,简化主流程
5. 协议费mintFee()公式
也就是:
其中:
6. burn流程的协议费计算
虽然burn的时候K下降,直觉来讲不存在协议费,但swap不会结算协议费,且不更新KLast。
如果burn之前存在swap操作,burn内部的_mintFee()能计算出当前K>KLast,进而结算协议费。形式同样是增发share给feeto地址
7. Core部分代码注释
UniswapV2Factory.sol
getPair
1
2// 根据两个token地址来查询pair地址
mapping(address => mapping(address => address)) public getPair;setFeeTo()
设置协议费接收地址
setFeeToSetter()
转让协议费设置权限
CreatePair()
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
28function createPair(address tokenA, address tokenB) external returns (address pair) {
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
//获取 UniswapV2Pair 合约的创建字节码
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
//计算出pair地址,不需要链上查询
//0:没有提供额外ETH
//add(bytecode, 32):跳过 bytes 类型前的 32 字节长度描述,指向真实字节码内容
//mload(bytecode):字节码长度
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
//初始化pair合约,绑定这俩token
IUniswapV2Pair(pair).initialize(token0, token1);
//保证(A,B),(B,A)是同一个pair
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
UniswapV2Pair.sol
变量说明
名称 表示什么 单位 reserve0/1 pair 合约里登记的 token0/1 储备量 token0/1 数量 balance0/1 实际 pair 地址持有的 token0/1 数量(链上余额) token0/1 数量 amount0/1 某次操作中使用的 token0/1 数量 token0/1 数量 totalSupply 当前发行的所有 LP token 数量 LP token 数量 liquidity 当前 mint/burn 操作涉及的 LP token 份额 LP token 数量 _update()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// update reserves and, on the first call per block, price accumulators
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; // overflow is desired
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// * never overflows, and + overflow is desired
//priceXCumulativeLast供预言机使用
//加权平均时间价格TWAP = ∆price0CumulativeLast / ∆timeElapsed
//price0CumulativeLast → 累积的「token1 per token0」价格 * 时间
//price0CumulativeLast = price0 * timeElapsed = _reserve1 / _reserve0 * timeElapsed
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);
}_mintFee()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24//如果sqrt(k)增长了,说明池子增长了,协议就要分走「净增长」的1/6
// 每次swap交易收取协议费太耗费gas, 所以只在每次添加和移除流动性的时候才进行协议费计算
// mint的时候k上涨,手续费是凭空增发 涨幅1/6 的LP token给协议,不扣用户的钱
// burn的时候如果因为之前存在swap手续费导致k上涨,那么此时也会增发协议费
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
uint _kLast = kLast; // gas savings
if (feeOn) {
if (_kLast != 0) {
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); // 计算当前的
uint rootKLast = Math.sqrt(_kLast); // 计算上次的
if (rootK > rootKLast) {
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
//如果没开启协议费,不再记录历史增长
} else if (_kLast != 0) {
kLast = 0;
}
}mint()
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
47
48
49// mint是铸造LP token给用户,不是给池子转钱
// this low-level function should be called from a contract which performs important safety checks
function mint(address to) external lock returns (uint liquidity) {
//在调用mint之前,addLiquidity已经完成了转账
//所以当前的balance是reserve + LP token
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
//计算应该给用户多少 LP token
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
//是否开启协议费(protocol fee)
//如果开启了协议费,则会凭空铸造(增发)1/6 LP token给feeTo地址,并不是直接扣用户的钱
bool feeOn = _mintFee(_reserve0, _reserve1);
//totalSupply会在_mintFee中增加
//Q: 为什么会增加? A:把 LP Token 想象成“基金单位”,而池子里的资金是基金资产。
// 当作为基金管理方(治理)抽取管理费时,不是从现有资产里拿走现金,而是多印一些基金份额归自己。
//这里必须在下一次调用_mintFee之前把旧的_totalSupply缓存下来,供后续计算使用
uint _totalSupply = totalSupply; // gas savings
if (_totalSupply == 0) {
//如果第一次创建池子,sqrt(x * y) 作为初始流动性基准
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
//锁住 MINIMUM_LIQUIDITY(通常是 1000 wei),mint 给 0 地址,防止除零/攻击。
_mint(address(0), MINIMUM_LIQUIDITY);
/*
按比例算出用户应得的 LP token:
看这次新增 token 相对于之前池子 token 的比例。
两边取较小值(避免滑点破坏池子平衡)。
*/
liquidity = Math.min(
amount0.mul(_totalSupply) / _reserve0,
amount1.mul(_totalSupply) / _reserve1
);
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
//给用户mint计算好的LP token
_mint(to, liquidity);
//更新储备值(把旧 reserve 更新为新余额)
_update(balance0, balance1, _reserve0, _reserve1);
//如果协议费开启,更新 kLast,供后续 mintFee 计算用
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1);
}burn()
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
39function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
//把token0和token1的地址存到局部变量中,减少多次SLOAD,节省gas
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
//获取pair实际持有的token数量
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
//获取pair实际持有的LP token数量。这是removeLiquidity的调用者transfer进来准备销毁的
uint liquidity = balanceOf[address(this)];
//如果协议开了fee,就先分给协议方对应的LP token
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
//计算amount使用balance,保证按照实际比例退钱
// amount = ( 你这次销毁的LP token / 总LP token )* 池子余额
// 除法向下取整,先乘再除是为了减少精度损失
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
//pair销毁收到的LP token
_burn(address(this), liquidity);
//退钱
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
//更新余额
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to);
}swap()
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
46function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
//至少输出一种token
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
//输出金额不能大于储备
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
//每个局部变量都会分配一个stack slot,但编译器限制函数内局部变量最多只能用16个slot
//此处用block来限制_token作用域,避免stack too deep错误
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
//乐观转账
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
//data.length不为零,说明可能有flash swap , 去执行用户的回调函数
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
//balance是用户还钱之后池子的余额
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
//amount0In = balance0 - (_reserve0 - amount0Out)
//_reserve0 - amount0Out = balance0 - amount0In
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
//如果amount0In与amount1In都为0,则说明没有用户还够钱,revert
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
//限制作用域
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
//swap 后池子(扣除手续费)的乘积至少和 swap 前一样。否则存在套利,会revert
//换句话说,swap后k至少不变或者增长
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);
}skim()
1
2
3
4
5
6
7
8
9// 同步balance到reserve,把多余的token转给用户
// 捞出误打的token,避免被锁死在池子
// force balances to match reserves
function skim(address to) external lock {
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}sync()
1
2
3
4
5// 绕过uniswap逻辑直接打币进pair的话,reserve没更新,导致k=reserve0 * reserve1不准确,sync强制同步储备
// force reserves to match balances
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
8. Periphery部分代码注释
Router01
addLiquidity()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
// validate that the pair exists, if not it will be created
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
// send tokens to pair
// 这里的pairFor是根据tokenA,tokenB的地址用create2计算出来的,和UniswapV2Factory中的getPair逻辑一致
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
//将tokenA和tokenB从msg.sender转移到pair合约中
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
//调用pair合约的mint函数,铸造Liquidity Provider token(此处的liquidity)给to地址(流动性提供者)
liquidity = IUniswapV2Pair(pair).mint(to);
}_addLiquidity()
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
32function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) private returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
//需要维持恒定乘积,默认定a求b,但如果最佳b数量大于期望存入的b数量(b给的不够),就反过来定b求a
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
//如果b给的够
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
//如果b不够,定b求a,fallback逻辑
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
//防御性判断,既然b不够,那么a肯定够(在user提供的a,b之间取小于提供值的最佳值,而且成比例,所以b不够a肯定够)
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}addLiquidityETH()
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
27function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable override ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
//将token从msg.sender转移到pair合约中
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
//将ETH转入WETH合约,转换成WETH
IWETH(WETH).deposit{value: amountETH}();
//将WETH转移到pair合约中
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
//退还多余的ETH给msg.sender
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH); // refund dust eth, if any
}removeLiquidity()
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
26function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public override ensure(deadline) returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// send LP token to pair
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);
// burn LP token,transfer token0,1 to (to)
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
//pair内部用token0,token1的顺序来存储流动性,所以需要根据tokenA,tokenB的地址来排序
//根据tokenA,tokenB的地址排序,保证token0是地址小的那个
(address token0, ) = UniswapV2Library.sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
//防御性判断,确保取出的token数量不小于最小值
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}_swap()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function _swap(uint[] memory amounts, address[] memory path, address _to) private {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
//确定当前 pair 的 token0 和 token1,用于决定金额输出时应该放在哪个参数里
(address token0, ) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
//swap(amount0Out, amount1Out, to, data) 调用规范
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
//如果不是最后一个pair,则to是下一个pair的地址,否则是用户指定的地址
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
//执行实际的 swap,最后一个参数data,用于闪电贷,这里设为空
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out,
amount1Out,
to,
new bytes(0)
);
}
}swapExactTokensForTokens()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override ensure(deadline) returns (uint[] memory amounts) {
//amount是每个path交易之后的数量,0.3%手续费在getAmountsOut()中扣除
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
//把token转入第一跳的pair
TransferHelper.safeTransferFrom(
path[0],
msg.sender,
UniswapV2Library.pairFor(factory, path[0], path[1]),
amounts[0]
);
_swap(amounts, path, to);
}swapTokensForExactTokens()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//定amountOut求amountIn
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external 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);
}
Router02
_swapSupportingFeeOnTransferTokens()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19function _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;
{ // scope to avoid stack too deep errors
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
//实时读取 pair 中的余额减去原储备,得到真正进入的 input 数量,解决实际到账小于发送数的问题
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));
}
}removeLiquidityETHWithPermitSupportingFeeOnTransferTokens()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
//是否最大授权(true -> uint(-1))
uint value = approveMax ? uint(-1) : liquidity;
//v,r,s为owner对参数签名结果
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}removeLiquidityETHSupportingFeeOnTransferTokens()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin, //避免滑点太大
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
//直接调用balanceOf获取token真实数量。
//因为整个 Token 都已经转给了 Router(即使被扣了税),就按实际余额转出,不检查amountTokenMin
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
Library
pairFor()
1
2
3
4
5
6
7
8
9
10// calculates the CREATE2 address for a pair without making any external calls
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
}quote()
1
2
3
4
5
6
7//池子里放入 amountA 数量的 tokenA,理论上能换出多少数量的 tokenB(不包含 swap 手续费,也不考虑滑点,只是裸的价格比例)
// given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}getAmountOut()
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// given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
//reserveIn 是交换进池子的token在池子中储备量,reserveOut 是交换出的
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');
//收0.3%的手续费
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
/* note :
恒定乘积公式:
reserveIn * reserveOut = newReserveIn * newReserveOut = old_k
此时newReserveIn = reserveIn + amountInWithFee
newReserveOut = reserveOut - amountOut
带入后得到:
reserveIn * reserveOut = (reserveIn + amountInWithFee) * (reserveOut - amountOut)
reserveIn * reserveOut = (reserveIn + amountInWithFee) * reserveOut - (reserveIn + amountInWithFee) * amountOut
(reserveIn + amountInWithFee) * amountOut = (reserveIn + amountInWithFee) * reserveOut - reserveIn * reserveOut
(reserveIn + amountInWithFee) * amountOut = amountInWithFee * reserveOut
得到代码里的:amountOut = (amountInWithFee * reserveOut) / (reserveIn + amountInWithFee)
之后手续费会加入池子,k会增大
swap,addLiquidity增大k
removeLiquidity减小k
*/
amountOut = numerator / denominator;
}getAmountIn()
1
2
3
4
5
6
7
8
9// given an output amount of an asset and pair reserves, returns a required input amount of the other asset
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
//加 1 是为了解决整除向下取整的问题(精度保守估计,多给一点)
amountIn = (numerator / denominator).add(1);
}
- Title: UniswapV2 walkthrough
- Author: Chiu
- Created at : 2025-06-02 10:39:00
- Updated at : 2025-07-15 13:02:07
- Link: https://github.com/Idealist17/github.io/2025/06/02/UniswapV2/
- License: This work is licensed under CC BY-NC-SA 4.0.