UniswapV2-移除流动性


移除流动性,实际上是造市商进行 “资金” 赎回的一种方式。
造市商按照一定比例往 “资金池” 注入一定量的 token 对,以使其他人可以在这个池子中完成 token 兑换;赎回时,自然也是按照一定“参股”比例,收回自己原本注入的 token 对。

UniswapV2-创建 UniswapV2Pair
UniswapV2-添加流动性
UniswapV2-移除流动性
UniswapV2-兑换(swap)
UniswapV2-公式分析

一、概述

如下代码块所示,在移除移动性时,需要如下步骤(下述 pair 代表持有 token对 的合约):

  1. 确定当前要在哪个 pair 中移除流动性;
  2. 将用户的 lp token 转移到 pair 地址;
  3. pair 将收到的 lp token 销毁,并按照 pair 中 lp token 的总量,以及 tokenA 和 tokenB 的数量,将兑换出来的 tokenA 和 tokenB 发送到指定接收地址;
  4. 检查兑换出来的 tokenA 和 tokenB 的数量,是否满足预期设定,如不满足,则整个移除流动性的交易失败。

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
// https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L103
// **** REMOVE LIQUIDITY ****
/**
**@tokenA pool 中的一个 token
**@tokenB pool 中的另一个 token
**@liquidity 想要取出的流动量
**@amountAmin 移除流动性后,需收到的 tokenA 的最小量
**@amountBmin 移除流动性后,需收到的 tokenB 的最小量
**@to 接收相关资产的地址
**@deadline 交易完成所允许的最大时间
**/
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
// 获取当前 pair
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
// 从 msg.sender 转入 liquidity 到 pair
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
// 在 pair 中销毁 liquidity 后,将相关资产转移到 to 地址
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
// 地址对齐
(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');
}

二、将持有的 lp token 转移给 pair

通过 transferFrom 方法,将 lp token 发给 pair,后续将在 pair合约 内统一完成 lp token 销毁以及​ token对 的赎回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 上述代码块中,调用了 send liquidity to pair 的方法:
// IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);
//https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L73
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}

//https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2ERC20.sol#L57
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}

三、在 pair 中销毁 lp token 及 token 赎回

先删除用户的 lp token,并根据其 “占股” 情况,返回​用户的 token对。
可以注意到,仅在 lp token 添加和销毁的时候,会涉及到协议费的相关逻辑。用户在 “取现” 或者 “参股” 的时候才会导致分红的变化,这个时候,平台就来 “薅” LP 羊毛了​(目前收费开关还没打开)。协议费的计算过程,在 添加流动性 时已经介绍,这里不再赘述。

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
//https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L134
// this low-level function should be called from a contract which performs important safety checks
function burn(address to) external lock returns (uint amount0, uint amount1) {
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
// pair 所持有的 _token0 的余额,这个值,这里应该就是 _reverse0
uint balance0 = IERC20(_token0).balanceOf(address(this));
// pair 所持有的 _token1 的余额,这个值,这里应该就是 _reverse1
uint balance1 = IERC20(_token1).balanceOf(address(this));
// 获取 lp token amount
uint liquidity = balanceOf[address(this)];

bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee

// 可以取回的 token0 的数量
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
// 可以取回的 token1 的数量
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
// 从 pair 中,删除 liquidity 的 lp token
_burn(address(this), liquidity);
// 从 pair 向 to 地址转入 amount0 的 _token0
_safeTransfer(_token0, to, amount0);
// 从 pair 向 to 地址转入 amount1 的 _token1
_safeTransfer(_token1, to, amount1);
// pair 在 _token0 中的余额
balance0 = IERC20(_token0).balanceOf(address(this));
// pair 在 _token1 中的余额
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);
}

四、pair 数据更新

这里主要涉及到 pair 中,token 存量的更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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
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);
}

© 2024 YueGS