构建 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: MITpragma 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(含手续费) });});