在UniswapV1系统中,流动性提供者(LP)可以随时将其提供的流动性从交易池中移除。移除流动性的核心机制是通过销毁LP代币来获得相应比例的池内资产。
在 UniswapV1 系统中,流动性提供者(LP)可以随时将其提供的流动性从交易池中移除。移除流动性的核心机制是通过销毁 LP 代币来获得相应比例的池内资产。
/**
* @notice 移除流动性,返还相应比例的 ETH 和代币
* @param _amount 要销毁的 LP 代币数量
* @return ethAmount 返还的 ETH 数量
* @return tokenAmount 返还的代币数量
* @dev 根据 LP 代币在总供应量中的比例计算返还资产
*/
function removeLiquidity(uint256 _amount) public returns (uint256, uint256) {
// 验证输入参数的有效性
require(_amount > 0, "invalid amount");
// 计算用户应得的 ETH 数量
// 公式:用户ETH = (合约ETH余额 × 用户LP代币数量) ÷ LP代币总供应量
uint256 ethAmount = (address(this).balance * _amount) / totalSupply();
// 计算用户应得的代币数量
// 公式:用户代币 = (合约代币余额 × 用户LP代币数量) ÷ LP代币总供应量
uint256 tokenAmount = (getReserve() * _amount) / totalSupply();
// 销毁用户的 LP 代币
_burn(msg.sender, _amount);
// 向用户转账 ETH
payable(msg.sender).transfer(ethAmount);
// 向用户转账代币
IERC20(tokenAddress).transfer(msg.sender, tokenAmount);
return (ethAmount, tokenAmount);
}
/**
* @notice 获取合约中代币的余额
* @return 合约持有的代币数量
*/
function getReserve() public view returns (uint256) {
return IERC20(tokenAddress).balanceOf(address(this));
}
流动性移除的核心计算公式:
移除资产数量 = 池内总资产 × (用户LP代币数量 ÷ LP代币总供应量)
具体应用:
无常损失(Impermanent Loss)是指流动性提供者在移除流动性时,获得的资产总价值低于简单持有这些资产时的价值。这种损失的产生主要源于池内资产比例的变化。
假设初始状态:
价格变动后:
此时流动性提供者移除全部流动性,获得的资产总价值可能低于简单持有原始资产的价值。
每笔交易都会收取一定比例的手续费(通常为 0.3%),这些费用会留在流动性池中,增加池内资产的总量。
LP 代币的价值会随着交易费用的积累而增长:
LP代币价值 = (池内总ETH + 池内总代币价值) ÷ LP代币总供应量
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import "forge-std/Test.sol";
import "../src/Exchange.sol";
import "../src/Token.sol";
/**
* @title ExchangeLiquidityTest
* @notice 测试流动性移除和 LP 奖励机制
*/
contract ExchangeLiquidityTest is Test {
Exchange exchange;
Token token;
address liquidityProvider;
address trader;
function setUp() public {
// 创建测试地址
liquidityProvider = makeAddr("liquidityProvider");
trader = makeAddr("trader");
// 部署代币合约
token = new Token("Test Token", "TT", 1000000 * 10**18);
// 部署交易所合约
exchange = new Exchange(address(token));
// 为流动性提供者分配资产
vm.deal(liquidityProvider, 1000 ether);
token.transfer(liquidityProvider, 100000 * 10**18);
// 为交易者分配资产
vm.deal(trader, 100 ether);
}
/**
* @notice 测试完整的流动性生命周期
* @dev 包括添加流动性、交易、费用积累、移除流动性
*/
function testCompleteLiquidityCycle() public {
// 1. 流动性提供者添加初始流动性
vm.startPrank(liquidityProvider);
// 批准代币转账
token.approve(address(exchange), 200 * 10**18);
// 添加流动性:100 ETH + 200 代币
exchange.addLiquidity{value: 100 ether}(200 * 10**18);
// 验证初始状态
assertEq(exchange.balanceOf(liquidityProvider), 100 ether); // LP 代币数量
assertEq(address(exchange).balance, 100 ether); // 池中 ETH
assertEq(exchange.getReserve(), 200 * 10**18); // 池中代币
vm.stopPrank();
// 2. 交易者进行代币交换
vm.startPrank(trader);
// 用 10 ETH 换取代币,期望至少获得 18 个代币
uint256 traderTokensBefore = token.balanceOf(trader);
exchange.ethToTokenSwap{value: 10 ether}(18 * 10**18);
uint256 traderTokensAfter = token.balanceOf(trader);
// 验证交易结果
uint256 tokensReceived = traderTokensAfter - traderTokensBefore;
console.log("Tokens received by trader:", tokensReceived);
// 验证池状态变化
console.log("ETH in pool after trade:", address(exchange).balance);
console.log("Tokens in pool after trade:", exchange.getReserve());
vm.stopPrank();
// 3. 流动性提供者移除流动性
vm.startPrank(liquidityProvider);
uint256 lpTokens = exchange.balanceOf(liquidityProvider);
uint256 lpEthBefore = liquidityProvider.balance;
uint256 lpTokensBefore = token.balanceOf(liquidityProvider);
// 移除全部流动性
(uint256 ethReturned, uint256 tokensReturned) = exchange.removeLiquidity(lpTokens);
uint256 lpEthAfter = liquidityProvider.balance;
uint256 lpTokensAfter = token.balanceOf(liquidityProvider);
// 验证返还金额
assertEq(lpEthAfter - lpEthBefore, ethReturned);
assertEq(lpTokensAfter - lpTokensBefore, tokensReturned);
// 输出详细结果
console.log("ETH returned to LP:", ethReturned);
console.log("Tokens returned to LP:", tokensReturned);
console.log("ETH change:", ethReturned - 100 ether);
// 计算代币变化(可能是负数,所以使用条件判断)
if (tokensReturned >= 200 * 10**18) {
console.log("Token change (gain):", tokensReturned - 200 * 10**18);
} else {
console.log("Token change (loss):", 200 * 10**18 - tokensReturned);
}
vm.stopPrank();
// 4. 分析收益情况
// 流动性提供者获得了交易者支付的 ETH,但失去了相应的代币
// 同时获得了交易费用作为补偿
assertTrue(ethReturned > 100 ether, "Should get more ETH");
assertTrue(tokensReturned < 200 * 10**18, "Token amount should decrease");
}
/**
* @notice 测试多次交易的费用积累效果
*/
function testMultipleTradesFeeAccumulation() public {
// 添加初始流动性
vm.startPrank(liquidityProvider);
token.approve(address(exchange), 200 * 10**18);
exchange.addLiquidity{value: 100 ether}(200 * 10**18);
vm.stopPrank();
// 记录初始池价值
uint256 initialPoolValue = address(exchange).balance + exchange.getReserve() / 2;
// 进行多次小额交易
vm.startPrank(trader);
for (uint i = 0; i < 5; i++) {
exchange.ethToTokenSwap{value: 1 ether}(1 * 10**18);
}
vm.stopPrank();
// 计算最终池价值
uint256 finalPoolValue = address(exchange).balance + exchange.getReserve() / 2;
// 由于交易费用的积累,池的总价值应该增加
assertTrue(finalPoolValue > initialPoolValue, "Pool value should increase due to fee accumulation");
console.log("Initial pool value:", initialPoolValue);
console.log("Final pool value:", finalPoolValue);
console.log("Fee earnings:", finalPoolValue - initialPoolValue);
}
}
# 运行流动性测试
forge test --match-contract ExchangeLiquidityTest -v
# 查看详细输出
forge test --match-test testCompleteLiquidityCycle -vvv
完整的项目代码和更多实例请访问: https://github.com/RyanWeb31110/uniswapv1_tech
建议读者克隆项目代码,通过实际操作来深入理解流动性管理机制和 AMM 协议的工作原理。通过动手实践,您将更好地掌握去中心化交易所的核心技术。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!