在前面的章节中,我们实现了基础的 addLiquidity
函数,但存在一个重大问题:它允许用户以任意比例添加流动性,这会严重影响交易价格的稳定性。
本系列文章将带您从零构建一个 UniswapV1 去中心化交易所,深入理解 AMM(自动做市商)机制的核心原理。
在前面的章节中,我们实现了基础的 addLiquidity
函数,但存在一个重大问题:它允许用户以任意比例添加流动性,这会严重影响交易价格的稳定性。
目前的 addLiquidity
函数实现如下:
function addLiquidity(uint256 _tokenAmount) public payable {
IERC20 token = IERC20(tokenAddress);
token.transferFrom(msg.sender, address(this), _tokenAmount);
}
核心问题:该函数允许用户随时以任意比例添加流动性,这会破坏价格机制。
在 AMM 机制中,汇率由储备比例决定:
P_ETH = tokenReserve / ethReserve
P_TOKEN = ethReserve / tokenReserve
其中:
P_ETH
和 P_TOKEN
分别是 ETH 和代币的价格ethReserve
和 tokenReserve
分别是 ETH 和代币的储备量价格稳定机制确保:
改进后的 addLiquidity
函数需要处理两种场景:
/**
* @dev 向流动性池添加资金(完善版本)
* @param _tokenAmount 用户提供的代币数量上限
* @notice 需要同时发送 ETH 和代币,追加流动性时比例必须匹配当前储备比例
*/
function addLiquidity(uint256 _tokenAmount) public payable {
if (getReserve() == 0) {
// 分支1:初始化流动性池 - 允许任意比例
// 这是设定初始价格的关键时刻
IERC20 token = IERC20(tokenAddress);
token.transferFrom(msg.sender, address(this), _tokenAmount);
} else {
// 分支2:追加流动性 - 必须维持现有比例
uint256 ethReserve = address(this).balance - msg.value;
uint256 tokenReserve = getReserve();
// 基于用户提供的 ETH 数量计算所需的代币数量
// tokenAmount = (msg.value × tokenReserve) ÷ ethReserve
uint256 tokenAmount = (msg.value * tokenReserve) / ethReserve;
// 确保用户提供了足够的代币
require(_tokenAmount >= tokenAmount, "insufficient token amount");
// 只转入计算得出的精确代币数量,多余部分不使用
IERC20 token = IERC20(tokenAddress);
token.transferFrom(msg.sender, address(this), tokenAmount);
}
}
uint256 ethReserve = address(this).balance - msg.value;
重要细节:需要从当前余额中减去 msg.value
,因为在 payable
函数中,发送的 ETH 已经被加入合约余额。
uint256 tokenAmount = (msg.value * tokenReserve) / ethReserve;
数学原理:
tokenReserve : ethReserve
msg.value
msg.value × (tokenReserve / ethReserve)
require(_tokenAmount >= tokenAmount, "insufficient token amount");
保护作用:确保用户提供了足够的代币,防止因代币不足导致的交易失败。
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/Exchange.sol";
import "../src/Token.sol";
contract LiquidityTest is Test {
Exchange exchange;
Token token;
address user = makeAddr("user");
address user2 = makeAddr("user2");
function setUp() public {
token = new Token("Test Token", "TEST", 1000000 ether);
exchange = new Exchange(address(token));
// 为测试用户分配足够的代币和 ETH
token.transfer(user, 5000 ether);
token.transfer(user2, 5000 ether);
vm.deal(user, 3000 ether);
vm.deal(user2, 3000 ether);
}
}
function testInitialLiquidity() public {
vm.startPrank(user);
// 1. 添加初始流动性:任意比例
token.approve(address(exchange), 1000 ether);
exchange.addLiquidity{value: 500 ether}(1000 ether);
// 2. 验证储备状态
assertEq(address(exchange).balance, 500 ether, "ETH reserve incorrect");
assertEq(exchange.getReserve(), 1000 ether, "Token reserve incorrect");
// 3. 验证初始价格设定
// 价格比例:1000 Token : 500 ETH = 2:1
uint256 expectedPrice = exchange.getPrice(1000 ether, 500 ether);
assertEq(expectedPrice, 2000, "Initial price incorrect"); // 2.0 with precision
vm.stopPrank();
}
function testProportionalLiquidity() public {
// 第一个用户添加初始流动性
vm.startPrank(user);
token.approve(address(exchange), 1000 ether);
exchange.addLiquidity{value: 500 ether}(1000 ether);
vm.stopPrank();
// 第二个用户按比例添加流动性
vm.startPrank(user2);
// 当前比例:1000 Token : 500 ETH = 2:1
// 如果添加 100 ETH,应该需要 200 Token
token.approve(address(exchange), 250 ether); // 提供足够的授权
exchange.addLiquidity{value: 100 ether}(250 ether);
// 验证比例添加成功
assertEq(address(exchange).balance, 600 ether, "Total ETH incorrect");
assertEq(exchange.getReserve(), 1200 ether, "Total tokens incorrect");
// 验证价格比例不变
uint256 newPrice = exchange.getPrice(1200 ether, 600 ether);
assertEq(newPrice, 2000, "Price ratio changed"); // 仍然是 2:1
vm.stopPrank();
}
function testInsufficientTokens() public {
// 添加初始流动性
vm.startPrank(user);
token.approve(address(exchange), 1000 ether);
exchange.addLiquidity{value: 500 ether}(1000 ether);
vm.stopPrank();
// 尝试添加流动性但代币不足
vm.startPrank(user2);
// 当前比例需要 200 Token,但只提供 150 Token
token.approve(address(exchange), 150 ether);
vm.expectRevert("insufficient token amount");
exchange.addLiquidity{value: 100 ether}(150 ether);
vm.stopPrank();
}
function testExactTokenUsage() public {
// 添加初始流动性
vm.startPrank(user);
token.approve(address(exchange), 2000 ether);
exchange.addLiquidity{value: 1000 ether}(2000 ether);
vm.stopPrank();
// 记录用户2的初始余额
vm.startPrank(user2);
uint256 initialTokenBalance = token.balanceOf(user2);
// 提供多余的代币授权,但只应使用精确数量
token.approve(address(exchange), 500 ether); // 授权500,但只需要200
exchange.addLiquidity{value: 100 ether}(500 ether);
// 验证只使用了精确的代币数量
uint256 finalTokenBalance = token.balanceOf(user2);
uint256 tokensUsed = initialTokenBalance - finalTokenBalance;
assertEq(tokensUsed, 200 ether, "Should use exact token amount");
vm.stopPrank();
}
# 运行所有流动性相关测试
forge test --match-test "testInitialLiquidity|testProportionalLiquidity|testInsufficientTokens|testExactTokenUsage" -v
# 查看详细的测试输出
forge test --match-test testProportionalLiquidity -vvv
在真实的 UniswapV1 实现中,流动性提供者会获得 LP(Liquidity Provider)代币作为凭证:
// 注意:这是概念性代码,实际实现需要 ERC20 标准
mapping(address => uint256) public liquidityBalances;
function addLiquidity(uint256 _tokenAmount) public payable {
if (totalLiquidity == 0) {
// 初始流动性:LP代币 = sqrt(ETH * Token)
uint256 liquidity = sqrt(msg.value * _tokenAmount);
liquidityBalances[msg.sender] = liquidity;
totalLiquidity = liquidity;
} else {
// 按比例分配:LP代币 = (新增ETH / 总ETH) * 总LP代币
uint256 liquidity = (msg.value * totalLiquidity) / address(this).balance;
liquidityBalances[msg.sender] += liquidity;
totalLiquidity += liquidity;
}
}
流动性提供者通过以下方式获得收益:
在实际实现中需要注意 Solidity 的整数除法精度问题:
// 使用更高的精度来避免舍入误差
uint256 constant PRECISION = 1e18;
uint256 tokenAmount = (msg.value * tokenReserve * PRECISION) / (ethReserve * PRECISION);
uint256 constant MINIMUM_LIQUIDITY = 1000;
function addLiquidity(uint256 _tokenAmount) public payable {
require(msg.value > MINIMUM_LIQUIDITY, "Insufficient ETH amount");
require(_tokenAmount > MINIMUM_LIQUIDITY, "Insufficient token amount");
// ... 其他逻辑
}
modifier nonReentrant() {
require(!locked, "ReentrancyGuard: reentrant call");
locked = true;
_;
locked = false;
}
function addLiquidity(uint256 _tokenAmount) public payable nonReentrant {
// ... 函数逻辑
}
本章完善了 UniswapV1 的流动性管理机制:
通过这些改进,我们的去中心化交易所获得了稳定可靠的流动性管理能力,为后续的高级功能奠定了坚实基础。流动性提供者可以安全地参与到 AMM 生态系统中,享受去中心化金融带来的收益机会。
完整项目代码请访问: https://github.com/RyanWeb31110/uniswapv1_tech
本系列文章是基于该项目的完整教学实现,欢迎克隆代码进行实践学习!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!