DeFi 协议开发实战:从 Uniswap V2 恒定乘积公式 x * y = k 到自定义 AMM 流动性池算子实现 DeFi 协议开发实战从 Uniswap V2 恒定乘积公式 x * y k 到自定义 AMM 流动性池算子实现在去中心化金融DeFi生态系统中自动做市商AMM, Automated Market Maker是流动性兑换的底层引擎。不同于传统订单簿Order Book依赖买卖双方的显式报价AMM 依靠智能合约内的数学算法实现了去中心化、无需许可的即时交易。以 Uniswap V2 为代表的经典 AMM 协议开创性地采用了**恒定乘积Constant Product Market Maker**公式。理解这套数学模型的推导机制并能够手写实现其流动性计价与份额代币LP Token分配逻辑是掌握 DeFi 智能合约开发的必修课。本文将深度剖析 Uniswap V2 的核心算数原理并用 Solidity 实现一个完整的 AMM 流动性池合约。一、 恒定乘积公式与兑换数学模型恒定乘积 AMM 的核心数学模型是$$x \times y k$$其中$x$资金池内代币 AToken0的储备量Reserve。$y$资金池内代币 BToken1的储备量Reserve。$k$在不发生流动性添加或提取的前提下乘积 $k$ 保持恒定不变。1.1 兑换数量Out Amount公式推导假设交易者存入数量为 $\Delta x$ 的 Token0以此换取数量为 $\Delta y$ 的 Token1。为了维持 $k$ 值恒定必须满足$$(x \Delta x) \times (y - \Delta y) k$$将 $k x \times y$ 代入$$(x \Delta x) \times (y - \Delta y) x \times y$$展开公式$$x \cdot y - x \cdot \Delta y \Delta x \cdot y - \Delta x \cdot \Delta y x \cdot y$$消去左右两边的 $x \cdot y$并整理出 $\Delta y$$$\Delta x \cdot y (x \Delta x) \cdot \Delta y$$$$\Delta y \frac{y \cdot \Delta x}{x \Delta x}$$在实际生产中协议通常会对注入的代币征收交易手续费例如 Uniswap V2 收取 0.3% 的手续费。若将手续费率定义为 $f$如 $f 0.003$则实际参与兑换的代币量为 $\Delta x \times (1 - f)$。兑换公式演变为$$\Delta y \frac{y \cdot \Delta x \cdot (1 - f)}{x \Delta x \cdot (1 - f)}$$二、 AMM 运作机制与数据流一个标准的 AMM 流动性池主要包含以下三个核心动作flowchart TD subgraph AMM_Engine [AMM 引擎] K[Constant K x * y] R0[Reserve0 x] R1[Reserve1 y] end User_Add([添加流动性]) --|注入 x 和 y 数量| Mint_LP[按比例增量铸造 LP Token] User_Remove([提取流动性]) --|销毁 LP Token| Burn_LP[按比例退回 x 和 y 余额] User_Swap([兑换 Swap]) --|注入 dx| Calc{计算 dy 并更新 K} Calc --|扣除 0.3% 手续费| Out_dy[给用户划转 dy 额度] Calc --|累加 dx 储备| R02.1 流动性代币LP Token的分配模型当用户向池内首次注入流动性时为了衡量其出资比例合约会向其铸造一定数量的流动性代币Liquidity Provider Token。初次注入铸造量为注入量的几何平均值即 $S_{minted} \sqrt{x \cdot y}$。后续追加入根据资产占当前总储备的比例的最小值确定避免套利即 $S_{minted} \min(\frac{\Delta x}{x} \cdot S_{total}, \frac{\Delta y}{y} \cdot S_{total})$。赎回提取根据用户销毁的 LP 数量占总供应量的比例退回资产即 $\Delta x \frac{S_{burn}}{S_{total}} \cdot x$。三、 价格滑点与无常损失价格滑点Slippage由于恒定乘积曲线的斜率随交易量变化而改变单笔交易量占池中储备比例越大交易执行价格就会越偏离当前市场价。这种现象即为滑点。无常损失Impermanent Loss当池中代币的外部市场汇率发生背离时套利者会入场通过低买高卖榨干价值使得流动性提供者LP的最终资产总值低于单纯持有现货的资产总值。只有当价格回归初始状态时这一损失才会消失。四、 工业级 AMM 资金池 Solidity 完整实现下面是一个完整的、符合生产级编译标准的 AMM 合约实现。合约集成了 ERC-20 基础代币作为 LP Token、恒定乘积兑换公式逻辑、按比例添加/提取流动性以及带 0.3% 手续费的兑换算子代码不包含任何占位符。// SPDX-License-Identifier: MIT pragma solidity 0.8.20; /** * dev 极简 ERC20 代币标准接口 */ interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); } /** * title 自定义恒定乘积 AMM 流动性池合约 */ contract ConstantProductAMM { IERC20 public immutable token0; IERC20 public immutable token1; uint256 public reserve0; uint256 public reserve1; uint256 public totalLPSupply; mapping(address uint256) public lpBalanceOf; event Mint(address indexed provider, uint256 amount0, uint256 amount1, uint256 lpAmount); event Burn(address indexed provider, uint256 amount0, uint256 amount1, uint256 lpAmount); event Swap(address indexed swapper, address indexed tokenIn, uint256 amountIn, uint256 amountOut); constructor(address _token0, address _token1) { token0 IERC20(_token0); token1 IERC20(_token1); } // // 内部数学辅助函数 // /** * dev 巴比伦求平方根算法 */ 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 x, uint256 y) internal pure returns (uint256) { return x y ? x : y; } // // 核心流动性动作 // /** * notice 添加流动性并铸造 LP Token */ function addLiquidity(uint256 _amount0Max, uint256 _amount1Max) external returns (uint256 lpShares) { // 先把资产从用户钱包拉入池中 token0.transferFrom(msg.sender, address(this), _amount0Max); token1.transferFrom(msg.sender, address(this), _amount1Max); uint256 balance0 token0.balanceOf(address(this)); uint256 balance1 token1.balanceOf(address(this)); uint256 amount0 balance0 - reserve0; uint256 amount1 balance1 - reserve1; // 计算分配的 LP 份额 if (totalLPSupply 0) { // 首次注入直接按几何平均值计算份额 lpShares _sqrt(amount0 * amount1); } else { // 后续追加按注入资产占池储备的较小比例计算防套利 lpShares _min( (amount0 * totalLPSupply) / reserve0, (amount1 * totalLPSupply) / reserve1 ); } require(lpShares 0, Insufficient liquidity created); // 铸造 LP 代币给用户 lpBalanceOf[msg.sender] lpShares; totalLPSupply lpShares; // 更新储备量 reserve0 token0.balanceOf(address(this)); reserve1 token1.balanceOf(address(this)); emit Mint(msg.sender, amount0, amount1, lpShares); } /** * notice 销毁 LP Token按比例回退双端代币 */ function removeLiquidity(uint256 _lpShares) external returns (uint256 amount0, uint256 amount1) { require(lpBalanceOf[msg.sender] _lpShares, Insufficient LP shares); // 依据份额比例计算返还额度 amount0 (_lpShares * reserve0) / totalLPSupply; amount1 (_lpShares * reserve1) / totalLPSupply; require(amount0 0 amount1 0, Liquidity amounts too small); // 销毁 LP Token lpBalanceOf[msg.sender] - _lpShares; totalLPSupply - _lpShares; // 更新状态并执行转账给用户 reserve0 - amount0; reserve1 - amount1; token0.transfer(msg.sender, amount0); token1.transfer(msg.sender, amount1); emit Burn(msg.sender, amount0, amount1, _lpShares); } // // 兑换核心算子 // /** * notice 执行代币兑换 (含 0.3% 手续费) * param _amountIn 存入的代币数量 * param _tokenIn 存入代币的地址 (必须为 token0 或 token1) */ function swap(uint256 _amountIn, address _tokenIn) external returns (uint256 amountOut) { require(_tokenIn address(token0) || _tokenIn address(token1), Invalid input token); require(_amountIn 0, Amount must be greater than 0); bool isToken0 _tokenIn address(token0); (uint256 reserveIn, uint256 reserveOut) isToken0 ? (reserve0, reserve1) : (reserve1, reserve0); // 划转输入代币 IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn); // 计算实际带手续费的输入额 (扣除 0.3% 手续费) // 997 / 1000 相当于乘以 0.997 uint256 amountInWithFee _amountIn * 997; // 分子: dy (reserveOut * dxWithFee) / (reserveIn * 1000 dxWithFee) uint256 numerator amountInWithFee * reserveOut; uint256 denominator (reserveIn * 1000) amountInWithFee; amountOut numerator / denominator; require(amountOut 0, Insufficient output amount); // 更新储备与划转输出 if (isToken0) { reserve0 token0.balanceOf(address(this)); reserve1 reserve1 - amountOut; token1.transfer(msg.sender, amountOut); } else { reserve1 token1.balanceOf(address(this)); reserve0 reserve0 - amountOut; token0.transfer(msg.sender, amountOut); } emit Swap(msg.sender, _tokenIn, _amountIn, amountOut); } }