跳转到内容

构建 AMM 流动性池

构建 AMM 流动性池

本页内容正在整理中,欢迎贡献

概述

本教程从零实现一个简化版的恒定乘积 AMM(自动做市商),帮助你深入理解 Uniswap V2 的核心机制:流动性提供、代币兑换和 LP Token。

主要内容

核心数学

恒定乘积公式: x × y = k
设流动性池有:
reserveA = A 代币数量
reserveB = B 代币数量
k = reserveA × reserveB
用 amountIn 的 A 换 B:
新 reserveA' = reserveA + amountIn * (1 - fee)
新 reserveB' = k / reserveA'
amountOut = reserveB - reserveB'
即: amountOut = reserveB * amountIn * (1 - fee) / (reserveA + amountIn * (1 - fee))

简化版 AMM 合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
/**
* @title SimpleAMM
* @dev 简化版 AMM,实现 x * y = k 恒定乘积做市商
* LP Token 代表池中的流动性份额
*/
contract SimpleAMM is ERC20 {
IERC20 public immutable tokenA;
IERC20 public immutable tokenB;
uint256 public reserveA;
uint256 public reserveB;
uint256 public constant FEE_NUMERATOR = 997; // 0.3% 手续费
uint256 public constant FEE_DENOMINATOR = 1000;
uint256 public constant MINIMUM_LIQUIDITY = 1000; // 防止第一个 LP 操控
event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
event LiquidityRemoved(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
event Swap(address indexed user, address tokenIn, uint256 amountIn, uint256 amountOut);
error InsufficientLiquidity();
error InsufficientOutputAmount();
error InvalidK();
constructor(address _tokenA, address _tokenB)
ERC20("AMM LP Token", "AMM-LP")
{
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
// ===== 流动性管理 =====
/**
* @dev 添加流动性,获取 LP Token
* @param amountADesired 希望存入的 A 数量
* @param amountBDesired 希望存入的 B 数量
* @param amountAMin 最少接受的 A 数量(滑点保护)
* @param amountBMin 最少接受的 B 数量
*/
function addLiquidity(
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
// 计算实际存入量(维持当前比例)
if (reserveA == 0 && reserveB == 0) {
amountA = amountADesired;
amountB = amountBDesired;
} else {
uint256 amountBOptimal = (amountADesired * reserveB) / reserveA;
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, "Insufficient B amount");
amountA = amountADesired;
amountB = amountBOptimal;
} else {
uint256 amountAOptimal = (amountBDesired * reserveA) / reserveB;
require(amountAOptimal >= amountAMin, "Insufficient A amount");
amountA = amountAOptimal;
amountB = amountBDesired;
}
}
// 转入代币
tokenA.transferFrom(msg.sender, address(this), amountA);
tokenB.transferFrom(msg.sender, address(this), amountB);
// 计算 LP Token 数量
uint256 totalSupply_ = totalSupply();
if (totalSupply_ == 0) {
// 首次添加:LP = sqrt(amountA * amountB) - MINIMUM_LIQUIDITY
liquidity = Math.sqrt(amountA * amountB) - MINIMUM_LIQUIDITY;
_mint(address(1), MINIMUM_LIQUIDITY); // 永久锁定最小流动性
} else {
liquidity = Math.min(
(amountA * totalSupply_) / reserveA,
(amountB * totalSupply_) / reserveB
);
}
require(liquidity > 0, "Insufficient liquidity minted");
_mint(msg.sender, liquidity);
// 更新储备
reserveA += amountA;
reserveB += amountB;
emit LiquidityAdded(msg.sender, amountA, amountB, liquidity);
}
/**
* @dev 移除流动性,销毁 LP Token,取回代币
*/
function removeLiquidity(
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin
) external returns (uint256 amountA, uint256 amountB) {
uint256 totalSupply_ = totalSupply();
amountA = (liquidity * reserveA) / totalSupply_;
amountB = (liquidity * reserveB) / totalSupply_;
require(amountA >= amountAMin, "Insufficient A amount");
require(amountB >= amountBMin, "Insufficient B amount");
_burn(msg.sender, liquidity);
tokenA.transfer(msg.sender, amountA);
tokenB.transfer(msg.sender, amountB);
reserveA -= amountA;
reserveB -= amountB;
emit LiquidityRemoved(msg.sender, amountA, amountB, liquidity);
}
// ===== 代币兑换 =====
/**
* @dev 用固定数量的输入代币换取输出代币
*/
function swapExactTokensForTokens(
address tokenIn,
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut) {
require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token");
bool isAToB = tokenIn == address(tokenA);
(uint256 reserveIn, uint256 reserveOut) = isAToB
? (reserveA, reserveB)
: (reserveB, reserveA);
// 计算输出(含 0.3% 手续费)
amountOut = getAmountOut(amountIn, reserveIn, reserveOut);
require(amountOut >= amountOutMin, "Insufficient output amount");
// 执行转账
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
(isAToB ? tokenB : tokenA).transfer(msg.sender, amountOut);
// 更新储备
if (isAToB) {
reserveA += amountIn;
reserveB -= amountOut;
} else {
reserveB += amountIn;
reserveA -= amountOut;
}
// 验证 k 不减少(考虑手续费)
// 注意:实际 Uniswap V2 中 k 会因手续费而略微增加
emit Swap(msg.sender, tokenIn, amountIn, amountOut);
}
// ===== 查询函数 =====
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256 amountOut) {
require(amountIn > 0, "Insufficient input");
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
uint256 amountInWithFee = amountIn * FEE_NUMERATOR;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * FEE_DENOMINATOR + amountInWithFee;
amountOut = numerator / denominator;
}
function getSpotPrice() external view returns (uint256 priceAInB) {
require(reserveA > 0, "No liquidity");
return (reserveB * 1e18) / reserveA;
}
}

测试

describe("SimpleAMM", function () {
it("添加流动性并交换", async function () {
// 部署两个测试 Token
const Token = await ethers.getContractFactory("MockERC20");
const tokenA = await Token.deploy("Token A", "TKA");
const tokenB = await Token.deploy("Token B", "TKB");
const AMM = await ethers.getContractFactory("SimpleAMM");
const amm = await AMM.deploy(await tokenA.getAddress(), await tokenB.getAddress());
// 添加初始流动性:1000 TKA + 2000 TKB(初始价格 1 TKA = 2 TKB)
await tokenA.approve(await amm.getAddress(), parseEther("1000"));
await tokenB.approve(await amm.getAddress(), parseEther("2000"));
await amm.addLiquidity(parseEther("1000"), parseEther("2000"), 0, 0);
// 用 10 TKA 换 TKB
await tokenA.approve(await amm.getAddress(), parseEther("10"));
const amountOut = await amm.getAmountOut(parseEther("10"), parseEther("1000"), parseEther("2000"));
console.log("10 TKA → TKB:", ethers.formatEther(amountOut)); // 约 19.82 TKB(含手续费)
});
});

深入阅读