本文是 UniswapV2 深入解析系列的第三篇文章,深入讲解流动性移除的工作原理、LP 代币销毁机制,以及不平衡流动性提供的惩罚效应。
本文是 UniswapV2 深入解析系列的第三篇文章,深入讲解流动性移除的工作原理、LP 代币销毁机制,以及不平衡流动性提供的惩罚效应。
流动性移除是流动性提供的逆向操作,正如代币销毁与代币铸造互为逆向操作。当流动性提供者希望取回其提供的代币时,需要将持有的 LP 代币销毁,以换取池子中相应比例的底层代币。
流动性移除遵循严格的比例计算原则:
返还代币数量公式:
返还代币数量 = 储备代币数量 × (持有LP代币数量 / LP代币总供应量)数学表达式:
Amount_token = Reserve_token × (Balance_LP / TotalSupply_LP)核心原理:
UniswapV2 的流动性管理采用对称设计:
这种设计使得流动性管理变得简洁明了,核心合约只需专注于底层的代币操作逻辑。
/**
 * @notice 销毁 LP 代币,移除流动性
 * @dev 将对应比例的两种代币发送给指定地址
 * @param to 接收代币的地址
 * @return amount0 返还的 token0 数量
 * @return amount1 返还的 token1 数量
 */
function burn(address to) external returns (uint256 amount0, uint256 amount1) {
    address _token0 = token0; // 节省 gas
    address _token1 = token1; // 节省 gas
    uint256 balance0 = IERC20(_token0).balanceOf(address(this));
    uint256 balance1 = IERC20(_token1).balanceOf(address(this));
    uint256 liquidity = balanceOf(address(this));
    uint256 _totalSupply = totalSupply(); // 节省 gas
    // 使用余额确保按比例分配,防止捐赠攻击
    amount0 = (liquidity * balance0) / _totalSupply;
    amount1 = (liquidity * balance1) / _totalSupply;
    if (amount0 <= 0 || amount1 <= 0) revert InsufficientLiquidityBurned();
    // 销毁 LP 代币
    _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);
    emit Burn(msg.sender, amount0, amount1, to);
}关键设计决策:使用当前余额 balance0/balance1 而非储备金 reserve0/reserve1 进行计算。
原因分析:
_safeTransfer(token0, to, amount0);
_safeTransfer(token1, to, amount1);安全考虑:
// 1. 计算返还数量
// 2. 销毁 LP 代币
// 3. 转账代币
// 4. 更新储备金
// 5. 发出事件设计原理:
注意:原文提到"UniswapV2 不支持部分流动性移除",这实际上是不准确的。该函数销毁用户的全部 LP 代币,但用户可以通过先转移部分 LP 代币到其他地址来实现部分移除的效果。
当流动性提供者提供不平衡的流动性时,由于取最小值计算策略,会导致:
假设池子当前状态:
用户提供不平衡流动性:
LP代币计算:
liquidity0 = (200 × 100) / 100 = 200
liquidity1 = (100 × 100) / 100 = 100
liquidity = min(200, 100) = 100  // 取最小值结果分析:
/**
 * @notice 测试基本的流动性移除功能
 */
function testBurn() public {
    // 提供初始流动性
    token0.transfer(address(pair), 1 ether);
    token1.transfer(address(pair), 1 ether);
    pair.mint();
    // 移除所有流动性
    pair.burn();
    // 验证测试结果
    assertEq(pair.balanceOf(address(this)), 0);           // LP代币余额为0
    assertReserves(1000, 1000);                           // 只剩最小流动性
    assertEq(pair.totalSupply(), 1000);                   // 总供应只剩最小流动性
    assertEq(token0.balanceOf(address(this)), 10 ether - 1000); // 返还代币数量
    assertEq(token1.balanceOf(address(this)), 10 ether - 1000);
}测试要点:
/**
 * @notice 测试不平衡流动性提供的惩罚效应
 */
function testBurnUnbalanced() public {
    // 第一次:平衡流动性提供
    token0.transfer(address(pair), 1 ether);
    token1.transfer(address(pair), 1 ether);
    pair.mint();
    // 第二次:不平衡流动性提供
    token0.transfer(address(pair), 2 ether);  // 多提供token0
    token1.transfer(address(pair), 1 ether);
    pair.mint(); // 只获得1个LP代币
    // 移除所有流动性
    pair.burn();
    // 验证惩罚效果
    assertEq(pair.balanceOf(address(this)), 0);
    assertReserves(1500, 1000);  // 最小流动性按比例分布
    assertEq(pair.totalSupply(), 1000);
    // 关键:损失了500 wei的token0
    assertEq(token0.balanceOf(address(this)), 10 ether - 1500);
    assertEq(token1.balanceOf(address(this)), 10 ether - 1000);
}惩罚分析:
/**
 * @notice 测试多用户环境下的不平衡流动性惩罚
 */
function testBurnUnbalancedDifferentUsers() public {
    // 测试用户提供初始平衡流动性
    testUser.provideLiquidity(
        address(pair),
        address(token0),
        address(token1),
        1 ether,
        1 ether
    );
    // 验证初始状态
    assertEq(pair.balanceOf(address(this)), 0);
    assertEq(pair.balanceOf(address(testUser)), 1 ether - 1000);
    assertEq(pair.totalSupply(), 1 ether);
    // 当前用户提供不平衡流动性
    token0.transfer(address(pair), 2 ether);
    token1.transfer(address(pair), 1 ether);
    pair.mint(); // 只获得1个LP代币
    assertEq(pair.balanceOf(address(this)), 1);  // 注意:只有1个LP代币
    // 移除自己的流动性
    pair.burn();
    // 验证显著的惩罚效果
    assertEq(pair.balanceOf(address(this)), 0);
    assertReserves(1.5 ether, 1 ether);
    assertEq(pair.totalSupply(), 1 ether);
    // 关键:损失了0.5 ether的token0(25%的损失!)
    assertEq(token0.balanceOf(address(this)), 10 ether - 0.5 ether);
    assertEq(token1.balanceOf(address(this)), 10 ether);
}单用户场景:
多用户场景:
思考题:损失的0.5 ether token0最终由谁获得?
答案分析:
详细推理:
假设testUser后续移除流动性:
testUser的token0收益 = (1 ether - 1000) × 1.5 ether / 1 ether
                    = 约1.5 ether - 1500相比正常情况下的1 ether收益,testUser额外获得了约0.5 ether。
比例化设计
惩罚机制
安全设计
通过本文的深入分析,我们全面理解了 UniswapV2 流动性移除机制的设计原理和实现细节。这种机制通过精巧的数学设计,既保证了流动性提供者的公平待遇,又通过惩罚机制维护了池子的价格稳定性。
 
                如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!