2. 流动性添加
任务目标
在自动化做市商(AMM)合约中实现添加流动性功能:用户存入两种代币,合约计算应铸造的LP代币数量并更新储备量。本案例聚焦恒定乘积公式 x * y = k 的核心逻辑,不涉及价格操纵、手续费或治理等扩展功能。
核心实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface IERC20 {
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
}
contract SimpleAMM {
IERC20 public token0;
IERC20 public token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event AddLiquidity(address indexed provider, uint256 amount0, uint256 amount1, uint256 lpTokens);
event Sync(uint256 reserve0, uint256 reserve1);
constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
// 添加流动性:按当前储备比例存入两种代币
function addLiquidity(uint256 amount0, uint256 amount1) external returns (uint256 lpTokens) {
require(amount0 > 0 && amount1 > 0, "Amounts must be > 0");
// 计算应存入的配对数量(按当前储备比例)
if (reserve0 > 0 || reserve1 > 0) {
require(reserve0 > 0 && reserve1 > 0, "Invalid reserves");
uint256 amount1Optimal = (amount0 * reserve1) / reserve0;
require(amount1 >= amount1Optimal, "Insufficient amount1");
amount1 = amount1Optimal; // 只接受最优比例,多余退回用户
}
// 铸造LP代币数量
if (totalSupply == 0) {
lpTokens = sqrt(amount0 * amount1);
} else {
lpTokens = min(
(amount0 * totalSupply) / reserve0,
(amount1 * totalSupply) / reserve1
);
}
require(lpTokens > 0, "Zero LP tokens");
// 转账代币到合约
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(msg.sender, address(this), amount1);
// 更新储备量与LP供应量
reserve0 += amount0;
reserve1 += amount1;
totalSupply += lpTokens;
balanceOf[msg.sender] += lpTokens;
emit AddLiquidity(msg.sender, amount0, amount1, lpTokens);
emit Sync(reserve0, reserve1);
}
// 辅助函数
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
运行与验证
环境依赖:Remix IDE(或Hardhat + ethers.js),需准备两个测试ERC20代币合约(如标准OpenZeppelin ERC20)。
- 在Remix中部署两个测试ERC20代币,分别记录地址。
- 部署
SimpleAMM合约,构造函数传入两个代币地址。 - 向AMM合约地址转账测试代币(或直接调用
addLiquidity前先给用户授权)。 - 调用
addLiquidity(amount0, amount1),例如首次添加amount0=1000,amount1=2000。
验证方法:
- 调用
reserve0()和reserve1(),确认储备量已更新为存入数量。 - 调用
balanceOf(用户地址),确认LP代币数量大于0。 - 再次调用
addLiquidity时传入不成比例的数量(如amount0=500,amount1=2000),合约应自动将amount1调整为按比例的最优值(此处为1000),交易成功且储备量按比例增加。 - 检查事件
AddLiquidity和Sync是否被正确触发。
预期输出(首次添加1000/2000):
reserve0= 1000,reserve1= 2000totalSupply= sqrt(1000*2000) ≈ 1414(整数截断)- 用户LP余额 = 1414
关键说明
- 首次添加流动性时无比例约束,LP数量取
sqrt(amount0 * amount1),防止储备量被稀释。 - 非首次添加时,合约强制按当前储备比例存入,多余代币不会从用户账户扣除(因
transferFrom只转amount1调整后的值)。 - 本实现未包含滑点保护(
minLpTokens参数)和手续费逻辑,实际生产环境需补充。