本文旨在为资深工程师与技术负责人提供一份关于自动化做市商(AMM)的深度技术剖析。我们将绕开表面的概念介绍,直击其核心算法、架构权衡与工程实现。内容将从经典的恒定乘积公式出发,深入探讨其在计算机系统中的实现细节、数学原理背后的经济学直觉,以及在真实金融场景中必须面对的无常损失、套利攻击等残酷现实。最终,我们将沿着 Uniswap 等头部项目的演进路径,展望 AMM 架构的未来形态。
现象与问题背景:从订单簿到流动性池的范式转移
在数字货币交易所或任何传统金融市场(如股票、期货)中,价格发现与交易撮合的核心是中央限价订单簿(Central Limit Order Book, CLOB)。订单簿模型本质上是一个由买单(bids)和卖单(asks)构成的实时数据库,按价格优先、时间优先的原则进行匹配。这是一个高效、成熟且被验证了数十年的模型。然而,它有几个与生俱来的特性:
- 对流动性的高度依赖:订单簿的“厚度”(即在各个价位上的挂单数量)决定了其市场深度和抗冲击能力。一个稀疏的订单簿会导致巨大的买卖价差(spread)和极高的滑点(slippage)。为了维持流动性,交易所需要专业的做市商(Market Maker)持续不断地提供双边报价。
- 中心化与准入壁垒:订单簿的维护、撮合引擎的运行都需要一个高性能、低延迟的中心化服务器集群。这不仅构成了单点故障风险,也意味着做市商资格通常掌握在少数机构手中,普通用户无法参与其中。
- 非连续性流动性:在价格剧烈波动或所谓的“报价真空”区域,订单簿上可能不存在对手盘,导致交易无法执行。
当去中心化金融(DeFi)兴起时,将 CLOB 模型直接搬到区块链上变得不切实际。区块链,尤其是以太坊这样的通用智能合约平台,其状态变更(交易)的成本(Gas Fee)极高,且吞吐量(TPS)极低。每一次挂单、撤单、修改订单在 CLOB 模型中都意味着一次链上交易,这在成本和性能上是完全无法接受的。因此,DeFi 需要一种全新的、为链上环境而生的流动性解决方案——自动化做市商(AMM)应运而生。
AMM 的核心思想是颠覆性的:用算法替代订单簿,用流动性池替代专业的做市商。它允许任何用户将自己的闲置资产注入一个“池子”中,成为流动性提供者(Liquidity Provider, LP),并通过一个确定性的数学公式来完成资产的定价与交换。这不仅解决了链上 CLOB 的性能瓶颈,也极大地降低了做市的门槛,实现了流动性的民主化。
关键原理拆解:恒定乘积公式的数学之美
(切换到教授模式)
绝大多数早期 AMM 的基石是一个极其简洁而优美的数学公式——恒定乘积公式(Constant Product Formula),由 Uniswap V1 首次推广。其表达形式为:
x * y = k
这里的变量含义至关重要:
- x: 流动性池中资产 A 的储备量(reserve)。
- y: 流动性池中资产 B 的储备量。
- k: 一个常数,称为“不变量”(invariant)。在不考虑手续费的情况下,每次交易后 k 的值都必须保持不变。
这个公式描绘了一条双曲线。任何交易都相当于在这条曲线上移动一个点。让我们从计算机科学和经济学的基础原理来剖析这个公式如何驱动一个市场。
1. 价格发现机制
在一个连续的函数中,瞬时价格是其导数。对于 `y = k/x` 这条曲线,其任意点的斜率(导数)为 `dy/dx = -k/x^2`。由于 `k = x*y`,我们可以代入得到 `dy/dx = -y/x`。忽略负号(因为它仅表示两个变量的反向关系),池中的瞬时价格 `P` 可以被定义为 `P = y/x`。这意味着,两种资产的相对价格完全由它们在池中的储备量比例决定。这是一个内生的、由算法驱动的价格,而非外部预言机(Oracle)喂价。
2. 交易(Swap)的执行
假设一个用户想用 `Δx` 数量的资产 A 来购买资产 B。他将 `Δx` 放入池中,池中的资产 A 储备变为 `x’ = x + Δx`。为了维持乘积 `k` 不变,资产 B 的新储备量 `y’` 必须满足 `(x + Δx) * y’ = k`,因此 `y’ = k / (x + Δx)`。用户能够获得的资产 B 数量 `Δy` 就是旧储备和新储备的差值:`Δy = y – y’ = y – k / (x + Δx)`。这个过程完全是确定性的计算,无需任何对手方匹配。
3. 滑点(Slippage)的必然性
滑点是指交易的预期价格与实际执行价格之间的差异。在 AMM 中,滑点是内生的,并非系统缺陷。当一笔交易 `Δx` 发生时,交易前的瞬时价格是 `P_before = y/x`,但交易执行后,池子状态变为 `(x+Δx, y-Δy)`,此时的瞬时价格是 `P_after = (y-Δy)/(x+Δx)`。用户实际成交的平均价格是 `Δy/Δx`。由于双曲线的形状,`P_before > Δy/Δx > P_after`。交易量 `Δx` 越大,池子的储备 `x` 和 `y` 越小,这种价格移动就越剧烈,滑点也就越大。这在经济学上完美地反映了供给和需求的关系:对一种资产的大量购买会推高其价格。
4. 无常损失(Impermanent Loss)的数学解释
无常损失是 LP 的一个核心风险,它本质上是一种机会成本。假设一个 LP 向 ETH/USDC 池中注入流动性。当 ETH 价格相较于 USDC 上涨时,套利者会发现池中的 ETH 价格低于外部市场。他们会用 USDC 买入池中的 ETH,直到池内价格与外部市场一致。这个过程导致池中的 ETH 数量减少,USDC 数量增多。当 LP 取出流动性时,他们会得到比最初放入时更多的 USDC 和更少的 ETH。如果 LP 当初选择仅仅持有(HODL)原始的 ETH 和 USDC,其资产总值的增长可能会超过作为 LP 所获得的资产价值(包含手续费)。这个差值就是无常损失。
其损失程度可以被精确计算。假设初始价格为 `P_0`,结束价格为 `P_1`,价格变化率为 `r = P_1/P_0`。那么,无常损失占初始投资价值的百分比为:
IL = (2 * sqrt(r) / (1 + r)) – 1
从公式可以看出:
- 当 `r=1`(价格不变)时,`IL = 0`。
- 价格无论朝哪个方向变动(`r > 1` 或 `r < 1`),`IL` 都会是负数,代表损失。例如,价格翻倍(`r=2`),`IL ≈ -5.7%`;价格减半(`r=0.5`),`IL ≈ -5.7%`。价格波动越大,无常损失越大。
这个损失被称为“无常”是因为只要 LP 不撤出流动性且价格回归到初始点,这个损失就会消失。然而在现实中,这很少发生。LP 的收益模型本质上是在赌:交易手续费的累积 > 无常损失。
系统架构总览:一个链上银行的极简模型
一个典型的 AMM DEX(如 Uniswap V2)在架构上可以被视为一组互相协作的智能合约,部署在区块链上。我们可以将其描绘成一个分布式的、无需许可的链上银行。
- 工厂合约 (Factory Contract):
这是一个单例合约,其唯一职责是创建和注册新的交易对池。当有人想为一对之前不存在的代币(例如 MY_TOKEN/WETH)提供流动性时,他们会调用工厂合约的 `createPair` 函数。工厂会部署一个新的、独立的智能合约来管理这个特定交易对的流动性池,并记录新池的地址。这种设计模式(工厂模式)使得系统可以无限扩展,支持任意 ERC-20 代币组合,同时保持核心逻辑的统一和安全。
- 交易对合约 (Pair Contract):
每个交易对合约都是一个独立的、有状态的合约,它持有两种代币(如 `token0` 和 `token1`)的储备。它实现了 `x*y=k` 的核心逻辑,并包含了 `swap`、`mint`(添加流动性)、`burn`(移除流动性)等关键函数。此外,它本身也是一个 ERC-20 代币,发行代表流动性份额的 LP Token。
- 路由合约 (Router Contract):
用户通常不直接与交易对合约交互。路由合约是一个无状态的、作为用户入口的工具合约。它极大地改善了用户体验。例如,如果用户想用代币 A 换取代币 C,但市场上只有 A/B 和 B/C 的流动性池,路由合约会自动计算出最佳路径(A -> B -> C),并在单笔原子交易中完成这两次交换。它还负责处理 ETH 与 WETH(Wrapped ETH)之间的转换,以及设置交易截止时间(deadline)和滑点容忍度(slippage tolerance)等安全参数。
- 前端 DApp (Frontend):
这是一个标准的 Web 应用程序,通过 Ethers.js 或 Web3.js 等库与用户的钱包(如 MetaMask)和链上路由合约进行交互。它负责从链上读取数据(如储备量、价格),构建交易,并请求用户签名和发送。
整个系统的交互流程是:用户在前端 DApp 上发起一笔交易 -> DApp 调用路由合约 -> 路由合约根据交易路径与一个或多个交易对合约交互 -> 交易对合约更新内部的储备量并转移代币。这一切都在一笔区块链交易中原子性地完成,要么全部成功,要么全部失败回滚,保证了资金的安全性。
核心模块设计与实现:代码之下的精度与陷阱
(切换到极客工程师模式)
Talk is cheap, show me the code. 让我们深入到 AMM 最核心的 `swap` 和流动性管理函数的伪代码实现中,看看里面有哪些工程上的坑。
1. Swap 交易的核心计算
下面是一个简化版的 `swap` 逻辑,重点在如何计算 `amountOut`。
// pseudocode inspired by UniswapV2
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
require(amountIn > 0, "Insufficient input amount");
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
// 假设手续费为 0.3%
uint amountInWithFee = amountIn * 997; // 乘以 997 而不是 0.997,避免浮点数
uint numerator = amountInWithFee * reserveOut;
uint denominator = (reserveIn * 1000) + amountInWithFee;
amountOut = numerator / denominator;
}
function swap(uint amount0Out, uint amount1Out, address to, ...) internal {
// ... 前置检查 ...
// balanceBefore0, balanceBefore1 是转账前的余额
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
// 计算实际收到的 amountIn
uint amount0In = balance0 > _reserve0 ? balance0 - _reserve0 : 0;
uint amount1In = balance1 > _reserve1 ? balance1 - _reserve1 : 0;
// ... 根据 amountIn 计算应转出的 amountOut ...
// amountOutExpected = getAmountOut(amountIn, reserveIn, reserveOut);
// require(amountOutActual >= amountOutExpected, "INSUFFICIENT_OUTPUT_AMOUNT");
// ... 执行转账 ...
_update(balance0, balance1, _reserve0, _reserve1); // 更新储备量
}
工程坑点与细节:
- 无浮点数运算:智能合约(尤其是在 EVM 中)没有原生的浮点数支持。所有计算都必须使用定点数或纯整数来模拟。在 `getAmountOut` 中,0.3% 的手续费是通过将被乘数乘以 997,被除数乘以 1000 来实现的。这是 DeFi 开发中的标准操作。
- 运算顺序防精度丢失:在计算 `(amountInWithFee * reserveOut) / denominator` 时,必须先执行乘法再执行除法。如果先做除法,由于整数除法的截断效应,会造成巨大的精度损失。
- 防止不变量 `k` 减小:注意分母的计算 `(reserveIn * 1000) + amountInWithFee`。理论上,应该是 `reserveIn * 1000`。加上 `amountInWithFee` 是一种巧妙的设计,它确保了交易后的 `k’` 总是略大于等于交易前的 `k`,从而将手续费留在了池中,作为对 LP 的奖励。这是一个非常精妙的细节。
- 乐观转账与实际检查:早期的 AMM 实现是先计算再转账,但这容易受到重入攻击。Uniswap V2 采用了一种更安全的模式:它乐观地要求调用者先将代币转入池中,然后合约通过 `balanceOf` 检查实际到账的金额,并基于此计算需要转出的金额。这种“先收款再发货”的模式更加健壮。
2. 流动性铸造 (Mint) 与销毁 (Burn)
当 LP 添加流动性时,他们会获得 LP Token。这个过程叫 `mint`。
function mint(address to) internal returns (uint liquidity) {
uint reserve0 = _reserve0;
uint reserve1 = _reserve1;
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0 - reserve0;
uint amount1 = balance1 - reserve1;
uint _totalSupply = totalSupply; // LP token 总供应量
if (_totalSupply == 0) {
// 第一次添加流动性
liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
_mint(address(0), MINIMUM_LIQUIDITY); // 永久锁定少量流动性
} else {
liquidity = min(
(amount0 * _totalSupply) / reserve0,
(amount1 * _totalSupply) / reserve1
);
}
require(liquidity > 0, "INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity); // 给 LP 铸造 LP token
_update(balance0, balance1, reserve0, reserve1);
// ...
}
工程坑点与细节:
- 首次流动性提供者定价:第一个 LP 决定了池子的初始价格。如果一个恶意用户用 1 个 ETH 和 1 个假代币创建池子,他就能以极低成本操纵价格。为了缓解这个问题,Uniswap V2 在首次 `mint` 时会销毁少量(`MINIMUM_LIQUIDITY`)的 LP token,这略微提高了后续添加流动性的成本,算是一种轻微的攻击门槛。
- 按比例注入:后续的 LP 必须按照当前池中的 `reserve0/reserve1` 比例注入两种资产。`mint` 函数通过 `min` 函数确保了这一点,取两种资产能铸造的流动性的较小值,迫使用户按比例提供。
- 数据结构:在链上,交易对合约只需要存储 `reserve0`、`reserve1` 和 `totalSupply` 这几个关键状态变量。但是对于前端或数据分析平台来说,仅仅这些是不够的。它们需要监听合约触发的 `Sync`, `Swap`, `Mint`, `Burn` 事件,并将这些事件数据索引到数据库(如 The Graph)中,才能构建出历史价格曲线、交易量、TVL(总锁定价值)等图表。
对抗与权衡:在资本效率、无常损失与Gas成本间舞蹈
AMM 的设计是一系列精妙的权衡,不存在银弹。在实际应用中,它面临着多方面的挑战。
1. AMM vs. 订单簿 (CLOB)
- 资本效率:`x*y=k` AMM 的资本效率极低。流动性被均匀分布在 `(0, ∞)` 的整个价格曲线上。对于稳定币(如 USDC/DAI),其价格长期在 1.0 附近小幅波动,分布在 0.1 或 10.0 价格上的流动性几乎永远不会被用到,这是巨大的资本浪费。而 CLOB 的做市商可以将资金集中在当前市场价格的几个 tick 内。
- 用户体验:AMM 为小额交易者提供了极佳的体验——保证成交、无需理解复杂的订单类型。但对于大额交易(鲸鱼),AMM 的滑点是致命的,而一个深度好的 CLOB 可以提供更好的执行价格。
- 信息发现:CLOB 通过订单簿的买卖压力可以揭示市场情绪和预期。AMM 是一个“反应性”系统,它本身不产生价格信息,而是被动地被套利者驱动,使其价格与外部市场保持一致。
2. 无常损失 vs. 交易手续费
这是 LP 面临的核心经济博弈。在波动性大的市场(如山寨币),无常损失可能远超手续费收入,导致 LP 亏损。而在交易量大、价格相对稳定的市场(如 ETH/USDC 在一个盘整期),做 LP 才可能盈利。这要求 LP 具备一定的市场判断能力,而非一种“傻瓜式”的被动收入策略。
3. MEV 与三明治攻击
MEV (Miner Extractable Value) 是 AMM 架构在黑暗森林般的区块链环境中面临的最大威胁之一。当一个用户提交一笔大额交易时,这笔交易会先进入公开的内存池(Mempool)等待被矿工打包。监控 Mempool 的 MEV 机器人会发现这个机会:
- Front-running: 机器人以更高的 Gas 费提交一笔买单,抢在用户交易前成交,推高价格。
- Victim’s Trade: 用户的交易在更高的价格上执行,进一步推高价格。
- Back-running: 机器人立刻提交一笔卖单,以更高的 Gas 费确保在用户交易后成交,卖出之前买入的代币,锁定无风险利润。
用户的交易就像三明治中间的肉,被前后两笔交易夹击。这实质上是从用户身上榨取了价值。防御手段包括在前端设置严格的滑点容忍度、使用 Flashbots 等私密交易通道,或者采用 CowSwap 这样的防 MEV 交易协议。
架构演进之路:从Uniswap V1到V3及未来
AMM 的架构并非一成不变,它在持续演进以解决上述权衡中的痛点。
第一阶段: Uniswap V1 – 概念验证
V1 证明了 `x*y=k` 模型在以太坊上的可行性。但它非常初级,只支持 ETH 与单一 ERC-20 代币的交易对。这意味着如果你想用代币 A 换代币 B,你需要做两次交易:A -> ETH,然后 ETH -> B,这增加了 Gas 成本和操作复杂性。
第二阶段: Uniswap V2 – 行业标杆
V2 成为了 AMM 的黄金标准。它引入了几个关键改进:
- ERC20/ERC20 交易对: 通过内部使用 WETH 作为桥梁,实现了任意两个 ERC-20 代币之间的直接原子交换,极大地改善了可用性。
- 时间加权平均价格 (TWAP) 预言机: V2 开始在每个区块开始时累积记录价格。通过查询两个不同时间点的累积值之差,可以计算出这段时间内的 TWAP。这比直接使用瞬时价格作为预言机要安全得多,大大降低了价格被闪电贷攻击操纵的风险。
- 闪电兑换 (Flash Swaps): 允许用户在交易结束前“免费”借用池中的任何资产,只要他们在交易的最后一步将借用的资产(加上手续费)归还即可。这为套利和复杂的 DeFi 协议组合提供了强大的工具。
第三阶段: Uniswap V3 – 资本效率革命
V3 的核心创新是集中流动性 (Concentrated Liquidity),正面解决了 V2 资本效率低下的问题。
- 核心思想: LP 不再需要将其流动性分布在 `(0, ∞)` 的整个价格区间。他们可以选择一个自定义的价格范围(如,为 ETH/USDC 在 $3,000 到 $4,000 之间提供流动性)。当市场价格在这个范围内时,他们的流动性被激活并赚取手续费;当价格超出这个范围,他们的流动性就会被闲置(并且完全转换为范围边缘的其中一种资产)。
- 架构变化: 这背后是巨大的架构重构。`x*y=k` 的平滑曲线被离散化成无数个微小的价格区间,称为“刻度”(Ticks)。LP 的流动性被放置在这些刻度之间。交易对合约不再简单地存储 `reserve0` 和 `reserve1`,而是维护一个复杂的数据结构(一个与刻度相关的链表)来跟踪在每个价格点上可用的流动性。这使得 V3 的合约代码比 V2 复杂一个数量级。
- 带来的影响:
- 对 LP: 提供了潜在高出数百倍的资本效率,但要求更主动的管理策略。如果价格移出范围,LP 不仅赚不到手续费,其无常损失也会被放大。LP 的角色从被动变为主动。
- 对交易者: 在流动性集中的区域,交易者可以享受到比 V2 低得多的滑点,交易体验媲美中心化交易所。
- LP Token 变为 NFT: 由于每个 LP 的头寸(价格范围)都是独一无二的,代表其份额的凭证不再是同质化的 ERC-20 代币,而是一个非同质化代币(NFT)。
未来展望:主动做市与风险管理
AMM 的演进远未结束。未来的方向可能包括:
- 动态手续费: 手续费不再是固定的 0.3% 或其他值,而是根据市场波动性动态调整。高波动时期收取更高费用,以补偿 LP 承担的更高无常损失风险。
- 协议拥有的流动性 (Protocol Owned Liquidity, POL): DeFi 协议不再依赖外部 LP,而是通过自身的机制(如 OlympusDAO 的债券)来拥有和控制其核心交易对的流动性,以确保长期稳定。
- 主动做市策略: 出现更多基于 V3 的主动流动性管理协议(如 Arrakis Finance, Gamma),它们通过算法自动为用户调整和再平衡其流动性头寸,将复杂的 V3 操作简化,让普通用户也能享受其优势。
- 更精确的风险模型: 从“无常损失”这个略带误导性的词,转向更学术化的“损失与再平衡对比”(Loss-versus-Rebalancing, LVR),以更精确地量化和管理 LP 面临的套利风险。
总之,AMM 从一个简单的数学公式开始,已经演变成一个复杂、动态且充满活力的金融生态系统。理解其底层的算法原理、工程实现和架构权衡,是任何希望在 DeFi 领域构建稳健应用的工程师的必修课。
延伸阅读与相关资源
-
想系统性规划股票、期货、外汇或数字币等多资产的交易系统建设,可以参考我们的
交易系统整体解决方案。 -
如果你正在评估撮合引擎、风控系统、清结算、账户体系等模块的落地方式,可以浏览
产品与服务
中关于交易系统搭建与定制开发的介绍。 -
需要针对现有架构做评估、重构或从零规划,可以通过
联系我们
和架构顾问沟通细节,获取定制化的技术方案建议。