为什么需要AMM
在说AMM之前,先理解传统做市商的问题。
在股票、外汇这些传统金融市场,撮合买卖双方的是订单簿模式:买方出价、卖方报价,系统匹配后成交。这个模式需要专业的做市商持续报价、撤单、调价,对流动性要求很高。
区块链的匿名性和交易延迟让订单簿模式很难直接套用。 于是有人提出了一个天才的想法:与其让人工做市,不如让算法做市。
AMM(Automated Market Maker,自动做市商)的核心思想很简单:用数学公式替代订单簿,买卖价格由算法自动计算。用户不需要等待对手方,只需要和一个”资金池”交易即可。

恒定乘积做市商( CPMM )的数学原理
Uniswap采用的核心算法叫做恒定乘积做市商(Constant Product Market Maker),公式非常简洁:
plaintext
x * y = k
其中:
x= 资金池中Token A的数量y= 资金池中Token B的数量k= 恒定乘积(交易前后不变)
交易机制推导
假设交易前资金池有 (x₁, y₁),满足 x₁ * y₁ = k。
用户想用 Δx 个Token A换取Token B。交易后资金池变为 (x₂, y₂),需要满足:
plaintext
x₂ = x₁ + Δx (资金池多了Δx的Token A)
x₂ * y₂ = k (恒定乘积不变)
y₂ = k / x₂
因此用户获得的Token B数量为:
plaintext
Δy = y₁ - y₂
= y₁ - k / (x₁ + Δx)
= y₁ - (x₁ * y₁) / (x₁ + Δx)
= y₁ * (1 - x₁ / (x₁ + Δx))
= y₁ * Δx / (x₁ + Δx)
这就是AMM的定价公式。让我用一个具体例子验证:
假设资金池初始状态:x₁ = 1000 ETH,y₁ = 2000000 USDC,则 k = 2,000,000,000。
用户想用 Δx = 10 ETH 购买USDC:
plaintext
Δy = 2000000 * 10 / (1000 + 10) = 20000000 / 1010 ≈ 19801.98 USDC
即用户用10 ETH换到了约19802 USDC。价格约为 1 ETH = 1980.2 USDC,与当前市场价格基本一致。
大额交易的影响:滑点
继续上面的例子,如果用户要用 100 ETH 购买USDC呢?
plaintext
Δy = 2000000 * 100 / (1000 + 100) = 200000000 / 1100 ≈ 181818.18 USDC
用户得到了181818 USDC,但按市场价应该得到约198000 USDC(假设1 ETH = 1980 USDC)。差了约16000 USDC,这就是滑点。
滑点随着交易规模增大而急剧增加,这是AMM的核心限制之一。资金池越深(x和y越大),相同交易量的滑点越小。
无常损失:LP的核心风险
作为流动性提供者(LP),你需要理解一个关键概念:无常损失(Impermanent Loss)。
什么是无常损失
假设ETH价格是2000 USDC。你作为LP,向资金池投入了:
- 1 ETH(价值2000 USDC)
- 2000 USDC
资金池总价值:4000 USDC。你占总池子的0.1%(假设总池子价值400万USDC)。
价格变化后
假设ETH涨到4000 USDC。在AMM机制下,资金池会自动调整:
plaintext
x * y = k
x * y = 1000 * 2000000 = 2,000,000,000
当 x * 4000 = 2,000,000,000 时:
x = 500 ETH
y = 500,000 USDC
池子总价值 = 500 * 4000 + 500,000 = 2,500,000 USDC
如果你没有提供流动性,而是持有原来的1 ETH + 2000 USDC:
总价值 = 1 * 4000 + 2000 = 6000 USDC
无常损失 = 6000 – 5500 = 500 USDC(约8.3%)
数学公式
无常损失可以用一个简洁的公式表达:
plaintext
无常损失 = 2 * sqrt(价格比率) / (1 + 价格比率) - 1
当价格比率变为原来的k倍时:
| 价格变化 | 无常损失 |
|---|---|
| ±50% | -2.0% |
| ±100% | -5.7% |
| ±200% | -13.4% |
| ±400% | -25.5% |
| ±800% | -42.7% |
价格波动越大,无常损失越严重。 这也是为什么Uniswap V3允许LP集中流动性——他们希望用更高的手续费收益来对冲无常损失。
简化版AMM合约实现
现在来动手实现一个简化版的Uniswap AMM合约:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title SimpleAMM
* @notice 简化版恒定乘积做市商合约
*/
contract SimpleAMM {
// 代币A的储备量
uint256 public reserveA;
// 代币B的储备量
uint256 public reserveB;
// 两种代币的接口
IERC20 public tokenA;
IERC20 public tokenB;
// 手续费比例(基点,100 = 1%)
uint256 public fee = 30; // 0.3% 手续费
event Swap(
address indexed user,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut
);
event AddLiquidity(
address indexed provider,
uint256 amountA,
uint256 amountB
);
event RemoveLiquidity(
address indexed provider,
uint256 amountA,
uint256 amountB
);
constructor(address _tokenA, address _tokenB) {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
/**
* @notice 添加流动性(首次添加时初始化资金池)
*/
function addLiquidity(uint256 amountA, uint256 amountB) external returns (uint256 liquidity) {
require(amountA > 0 && amountB > 0, "Invalid amounts");
// 首次添加:初始化储备量
if (reserveA == 0 && reserveB == 0) {
tokenA.transferFrom(msg.sender, address(this), amountA);
tokenB.transferFrom(msg.sender, address(this), amountB);
reserveA = amountA;
reserveB = amountB;
liquidity = amountA; // 首次添加的liquidity代币数量
} else {
// 检查比例是否正确
require(
amountA * reserveB == amountB * reserveA,
"Invalid ratio"
);
tokenA.transferFrom(msg.sender, address(this), amountA);
tokenB.transferFrom(msg.sender, address(this), amountB);
// 按比例计算新增的liquidity
uint256 totalLiquidity = reserveA; // 简化:用reserveA代表总liquidity
liquidity = (amountA * totalLiquidity) / reserveA;
reserveA += amountA;
reserveB += amountB;
}
emit AddLiquidity(msg.sender, amountA, amountB);
}
/**
* @notice 移除流动性
*/
function removeLiquidity(uint256 liquidity) external returns (uint256 amountA, uint256 amountB) {
require(liquidity > 0, "Invalid liquidity");
uint256 totalLiquidity = reserveA; // 简化
amountA = (liquidity * reserveA) / totalLiquidity;
amountB = (liquidity * reserveB) / totalLiquidity;
reserveA -= amountA;
reserveB -= amountB;
tokenA.transfer(msg.sender, amountA);
tokenB.transfer(msg.sender, amountB);
emit RemoveLiquidity(msg.sender, amountA, amountB);
}
/**
* @notice 交换代币
* @param tokenIn 输入代币地址
* @param amountIn 输入数量
* @param amountOutMin 最小输出数量(防止大滑点)
*/
function swap(
address tokenIn,
uint256 amountIn,
uint256 amountOutMin
) external returns (uint256 amountOut) {
require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token");
require(amountIn > 0, "Invalid amount");
(uint256 reserveIn, uint256 reserveOut, address tokenOut) =
tokenIn == address(tokenA)
? (reserveA, reserveB, address(tokenB))
: (reserveB, reserveA, address(tokenA));
// 计算手续费(扣除0.3%)
uint256 amountInWithFee = amountIn * (1000 - fee);
// 恒定乘积公式计算输出
// Δy = (Δx * y) / (x + Δx)
amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
require(amountOut >= amountOutMin, "Slippage exceeded");
require(amountOut < reserveOut, "Insufficient reserve");
// 执行转账
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
IERC20(tokenOut).transfer(msg.sender, amountOut);
// 更新储备量
if (tokenIn == address(tokenA)) {
reserveA += amountIn;
reserveB -= amountOut;
} else {
reserveB += amountIn;
reserveA -= amountOut;
}
emit Swap(msg.sender, tokenIn, tokenOut, amountIn, amountOut);
}
/**
* @notice 获取当前价格(1个A能换多少B)
*/
function getPrice(address tokenIn) public view returns (uint256) {
require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token");
uint256 reserveIn = tokenIn == address(tokenA) ? reserveA : reserveB;
uint256 reserveOut = tokenIn == address(tokenA) ? reserveB : reserveA;
// 边际价格:dy/dx = y/x
return (reserveOut * 1e18) / reserveIn;
}
}
实际部署需要注意的问题
上面的简化版合约存在几个问题,生产环境需要额外处理:
1. 重入攻击防护
AMM合约涉及大量ETH/代币转账,必须加锁:
solidity
bool private locked;
modifier noReentrant() {
require(!locked, "Reentrancy detected");
locked = true;
_;
locked = false;
}
2. 闪电贷风险
简化版AMM无法防止闪电贷攻击。攻击者可以:
- 从池子借出大额资金
- 在其他交易所搬砖
- 把钱还回来
Uniswap V2通过k = x * y必须在交易后不变来防止这个问题:
solidity
function _update(uint256 balance0, uint256 balance1) private {
require(balance0 != 0 || balance1 != 0, "K invariant not satisfied");
// 验证k不变(允许小误差)
}
3. 精度问题
代币通常有不同的小数位数。在计算时必须考虑精度转换:
solidity
// 假设 tokenA 是 18 位小数,tokenB 是 6 位小数
// 存储时统一转为 18 位精度
uint256 public reserveA; // 18位精度
uint256 public reserveB; // 转换为18位精度存储
4. 权限控制
addLiquidity和removeLiquidity函数需要考虑谁有权调用。完整的AMM合约通常会引入工厂合约模式,由工厂创建交易对:
solidity
// 工厂合约创建交易对
function createPair(address tokenA, address tokenB) external returns (address pair) {
// ...
}
进阶话题:Uniswap V2 vs V3
Uniswap V3最大的创新是集中流动性(Concentrated Liquidity)。
传统V2中,LP的流动性均匀分布在整个价格曲线 [0, ∞) 上。但实际上,大多数交易都发生在某个特定价格区间。
V3允许LP将流动性集中在特定价格段,比如 [1800, 2200]。这意味着相同资金量可以获得更高的手续费收益,但代价是当价格偏离该区间时,你的流动性就不再被使用——需要承担更高的无常损失风险。
V3的数学公式变为:
plaintext
(x + Δx) * (y + Δy) = k // 只在这个价格段内成立
超出价格段时,该LP的资金会被完全换成一侧的资产,相当于”退出”了流动性池。
总结
AMM的核心原理其实很简洁:
- XY=K公式:交易前后恒定乘积不变
- 价格由池子深度决定:池子越大,滑点越小
- 无常损失是LP的主要风险:价格波动越大,损失越严重
理解这些基础概念后,你可以进一步研究:
- Uniswap V3的集中流动性机制
- Curve Finance的StableSwap公式(适合稳定币交易对)
- 自动做市商的安全漏洞和攻击向量
DeFi的世界很精彩,数学和代码的结合创造出了无限可能。希望这篇文章能成为你深入学习的起点。
相关推荐阅读:
