Uniswap V2 第 5 章:移除流动性(Remove Liquidity)
这一章是第 4 章的逆操作:销毁 LP token,按比例取回两种代币。Uniswap V2 的移除非常简单——纯粹按份额比例分两种币,不收任何手续费。我们讲清
burn的数学和它和铸造的对称性。
目录
- 1. 移除流动性的本质
- 2. burn 的核心公式
- 3. 案例:销毁 10% 的 LP
- 4. 为什么移除按比例、不收费
- 5. removeLiquidity 的完整流程与滑点保护
- 6. 你取回的不一定是当初存入的组合
- 7. 本章小结
- 8. 动手练习
1. 移除流动性的本质
你持有一些 LP token,想退出。销毁(burn)这些 LP,池子按你占总 LP 的比例,把两种代币都还给你一部分。
核心思想:你拿回的每种币 = 你的 LP 占比 × 池子该币的储备。
2. burn 的核心公式
pair.burn 的核心(简化):
amount0 = liquidity · balance0 / totalSupply
amount1 = liquidity · balance1 / totalSupply
liquidity:你销毁的 LP 数量。balance0/1:池子当前两种币的余额。totalSupply:LP 总量。
然后销毁你的 LP、把 amount0/amount1 转给你、更新储备。注意它用的是合约实际余额 balance(而非缓存的 reserve),这样任何”直接转入但没记账”的代币也会被一并按比例分掉。
3. 案例:销毁 10% 的 LP
设池子 balance0 = 100 WETH,balance1 = 300,000 DAI,totalSupply = 5,000 LP,你持有 500 LP(占 10%)。
amount_WETH = 500 · 100 / 5,000 = 10 WETH
amount_DAI = 500 · 300,000 / 5,000 = 30,000 DAI
你拿回 10 WETH + 30,000 DAI(池子的 10%)。简单、对称、无手续费。
4. 为什么移除按比例、不收费
- 按比例:你取出 10% 的 WETH 和 10% 的 DAI,池子取出后比例不变(k 等比例缩小),不影响价格。
- 不收费:因为没有制造任何”隐含兑换”(你没把一种币换成另一种,只是等比例抽走)。这和第 2 章 swap 收费、以及 Curve 的失衡费逻辑一致——只有”改变池子比例”的操作才收费,等比例进出不收费。
对比:如果你想”退出时只要一种币”,Uniswap V2 没有内置的单币退出(不像 Curve)。你得先 burn 拿回两种币,再自己去 swap 一次(那一步才付 swap 手续费)。
5. removeLiquidity 的完整流程与滑点保护
通过 Router 的 removeLiquidity:
function removeLiquidity(
address tokenA, address tokenB, uint256 liquidity,
uint256 amountAMin, uint256 amountBMin,
address to, uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
流程:
- 你先把 LP token
approve给 Router。 - Router 把你的 LP 转到 Pair,调用
pair.burn(to)。 - Pair 按第 2 节公式算出
amountA/amountB,转给to。 - 滑点保护:
require(amountA >= amountAMin && amountB >= amountBMin)。
amountAMin/amountBMin 防止你在交易被打包前价格/储备变动而取回过少。deadline 防止交易在内存池里搁置太久后才以陈旧价格执行。
6. 你取回的不一定是当初存入的组合
这是 LP 必须理解的一点:
- 你当初存入 10 WETH + 30,000 DAI(当时 1 WETH=3000 DAI)。
- 做 LP 期间,如果 ETH 涨价,套利者会从池子里买走 WETH、卖入 DAI,于是池子 WETH 变少、DAI 变多。
- 你退出时按当前储备比例取回——可能变成 8 WETH + 35,000 DAI 之类。
这种”取回组合随价格变化而偏移”的现象,叠加上你本可以单纯持币的收益,差额就是著名的无常损失(impermanent loss)。手续费收入是用来补偿无常损失的。本课程不展开 IL 数学,但要建立这个直觉。
7. 本章小结
- 移除 = 销毁 LP,按占比取回两种币:
amount = liquidity·balance/totalSupply。 burn用合约实际balance计算,等比例分配,不收手续费(没制造隐含兑换)。- Uniswap V2 没有单币退出;要单一币种得先 burn 再自行 swap。
- Router 的
removeLiquidity提供amountAMin/amountBMin滑点保护和deadline。 - 取回的组合按当前比例,可能偏离存入时的组合(无常损失的来源)。
8. 动手练习
对应课程的 Remove Liquidity 练习(和 Add 在同一个测试文件里)。
练习:removeLiquidity
主网分叉,先在 setUp 里 addLiquidity 拿到一些 LP,再移除。
interface IUniswapV2Router02 {
function removeLiquidity(
address tokenA, address tokenB, uint256 liquidity,
uint256 amountAMin, uint256 amountBMin,
address to, uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
}
思路:
setUp/ 测试开头:先addLiquidity(DAI, WETH, ...)拿到liquidity,记下来。- 把 LP token
approve给 Router:pair.approve(router, type(uint256).max)。 - 调用
removeLiquidity(DAI, WETH, liquidity, 1, 1, user, block.timestamp)。 - 打印返回的
amountA(DAI)、amountB(WETH)。 - 断言
pair.balanceOf(user) == 0(LP 烧光),且 user 的 DAI、WETH 余额都增加。
进阶
- 验证按比例:移除前读
getReserves和totalSupply,用第 2 节公式手算预期取回量,和实际返回对比。 - 体会无常损失(选做):在 add 之后、remove 之前,用一笔大额 swap 把池子价格推动一下,再 remove,观察取回的两种币组合相对存入时发生了偏移。
运行
forge test --evm-version cancun --fork-url $FORK_URL \
--match-path test/UniswapV2Liquidity.t.sol -vvv
下一章(第 6 章 Flash Swap)讲 Uniswap V2 的一个强大特性:先拿币、后付款的闪电兑换,以及它的手续费数学。