流动性池深度解析 — 从 AMM 原理到生产级 DeFi

流动性池深度解析 — 从 AMM 原理到生产级 DeFi

本指南为开发者和 DeFi 进阶用户提供对自动做市商(AMM)流动性池的系统性、专家级理解。从基本原理出发,逐步深入技术细节,覆盖在 EVM 环境中使用 Uniswap V2 兼容 DEX(如 PancakeSwap 及 CCBus 支持的其他 DEX)部署和管理流动性池所需的全部知识。


目录


1. 什么是流动性池?

流动性池是一个智能合约,持有两种代币的储备,并实现它们之间的去中心化、无需信任的交易。与传统订单簿(需要买卖双方同时匹配)不同,流动性池使用数学公式算法确定价格 — 这就是**自动做市商(AMM)**模型。

传统订单簿 vs AMM 流动性池 传统订单簿 卖单 100 @ $1.05 卖单 80 @ $1.03 卖单 50 @ $1.01 ── 买卖价差 ── 买单 40 @ $0.99 买单 60 @ $0.97 买单 200 @ $0.94 需要对手方匹配 vs AMM 流动性池 池子合约 储备 A: 10,000 TOKEN 储备 B: 100 WETH 价格 = 储备B / 储备A x · y = k(始终成立) 随时可交易,无需对手方

核心洞察: 池子随时可以交易。没有撮合引擎,无需等待买家或卖家。价格完全由池子中储备量的比率决定。


2. AMM 公式 — 恒积做市商

2.1 x·y = k 不变量

Uniswap V2 AMM 是恒积做市商,它强制执行一个简单的不变量:

1
x · y = k

其中:

  • x = 代币 A 的储备量
  • y = 代币 B 的储备量
  • k = 常数(永不减少,只随手续费增长)

交易发生时,代币流入流出,使得乘积 x · y 保持不变(手续费前)。这产生了特征性的双曲线价格曲线

x · y = k 恒积曲线 储备 X 储备 Y 0 100 200 300 400 当前状态 (x=200, y=50) 买入 X 后 (x=290, y=34.5) Δx 流入 Δy 流出 斜率 = 即时价格 k = 10,000

曲线是双曲线。 当你试图购买更多某种代币时,价格会非线性上涨 — 这就是价格影响

2.2 价格影响与滑点

执行交易时,你实际支付的价格是所经过曲线段的平均价格,而非即时价格:

1
2
3
4
即时价格(边际价格):price = y / x
执行价格(平均价格):price = Δy / Δx (始终比即时价格差)

相对于池子规模,交易量越大 → 价格影响越大
交易规模(占池子比例) 近似价格影响
0.01% ~0.01%(可忽略)
0.1% ~0.1%
1% ~1.01%
5% ~5.26%
10% ~11.11%
20% ~25%
50% ~100%(价格翻倍)

2.3 即时价格公式

1
2
3
4
5
6
X 以 Y 计的即时价格:P(X) = 储备Y / 储备X

示例:
储备X = 10,000 TOKEN
储备Y = 100 WETH
→ 1 TOKEN = 0.01 WETH = 100 TOKEN/WETH

关键点: 即时价格就是储备比率。任何改变储备量的交易都会改变价格。


3. Uniswap V2 合约架构

3.1 三合约模型

Uniswap V2(以及所有兼容 DEX,如 PancakeSwap V2)使用三层合约架构:

Uniswap V2 合约架构 👤 用户 / DApp msg.sender 调用 路由合约(Router) addLiquidity / removeLiquidity swapExactTokensForTokens / ... getPair/createPair mint/burn/swap 工厂合约(Factory) getPair(tokenA, tokenB) createPair → 部署交易对合约 交易对合约 #1 TOKEN / WETH reserve0, reserve1 ERC-20 LP 代币 交易对合约 #2 TOKEN / USDT reserve0, reserve1 ERC-20 LP 代币 通过 CREATE2 创建 各合约职责 路由:用户安全入口,滑点保护 + 截止时间 工厂:交易对注册表,每个代币组合对应一个交易对 交易对:持有储备,铸造/销毁 LP 代币,执行兑换

3.2 工厂合约 — 池子注册表

工厂合约是 DEX 所有池子的注册表,承担两个关键职责:

  1. getPair(tokenA, tokenB) — 返回交易对合约地址,不存在则返回 address(0)
  2. createPair(tokenA, tokenB) — 使用 CREATE2 部署新的交易对合约;若已存在则回滚

工厂会标准化代币排序(token0 < token1 按地址大小),因此 getPair(A, B)getPair(B, A) 始终返回同一个池子。

3.3 交易对合约 — 池子本体

每个交易对合约既是 ERC-20 LP 代币合约,同时持有池子的代币储备。关键接口:

函数 描述
mint(to) 代币转入后调用,铸造 LP 代币
burn(to) 销毁 LP 代币,按比例输出储备
swap(amount0Out, amount1Out, to, data) 代币转入后执行兑换
getReserves() 返回 (reserve0, reserve1, blockTimestampLast)
token0(), token1() 两个代币地址
totalSupply() 全部持有者的 LP 代币总量
balanceOf(address) 特定地址持有的 LP 代币数量

3.4 路由合约 — 用户入口

路由合约是面向用户的安全入口。用户应始终与路由合约交互,而非直接操作交易对合约。路由合约负责:

  • 验证滑点容忍度(amountMin 参数)
  • 强制执行交易截止时间
  • 透明处理 ETH ↔ WETH 包装/解包
  • 跨多个池子的多跳路由
  • 若池子不存在则处理 createPair 流程

4. 池子唯一性与确定性地址

4.1 池子唯一性的三要素

一个池子由三个要素唯一确定。钱包地址不是其中之一。

池子身份:三个必要因素 ① 代币对 TOKEN_A + TOKEN_B 顺序无关: getPair(A,B)==getPair(B,A) ✓ 按地址排序归一化 + ② DEX 工厂 PancakeSwap 工厂 vs ApeSwap 工厂 vs BiSwap 工厂 → 不同工厂 = 不同池子 + ③ 区块链网络 以太坊(ChainID: 1) BSC(ChainID: 56) Polygon(ChainID: 137) → 不同链 = 不同池子 = 一个确定性的池子地址 ❌ 不是唯一性因素:钱包地址 Alice(0xAaaa...1111)和 Bob(0xBbbb...2222)在相同工厂和网络上 共用完全相同的 TOKEN/WETH 池子。 你的钱包不会创建独立池子。你始终在向共享池子添加流动性。

4.2 CREATE2 确定性部署

池子地址是可预计算的 — 无需发送交易即可查询。工厂使用 CREATE2 操作码部署交易对合约,salt 由代币对派生:

1
2
3
4
salt     = keccak256(abi.encodePacked(token0, token1))
initHash = keccak256(pairBytecode)

pairAddress = keccak256(0xff ++ factoryAddress ++ salt ++ initHash)[12:]

这意味着:

  • 相同工厂上的相同代币对始终产生相同地址
  • 可以在任何交易之前离链计算交易对地址
  • 针对同一交易对的两个并发 createPair 调用:第二个会回滚,因为 CREATE2 目标地址已被占用

4.3 每个工厂每对代币只有一个池子

1
2
3
4
5
6
TOKEN/WBNB on PancakeSwap V2 = 池子 0xAAA...(一个池子,所有人共享)
TOKEN/WBNB on ApeSwap = 池子 0xBBB...(不同 DEX = 不同池子)
TOKEN/WETH on Ethereum = 池子 0xCCC...(不同链 = 不同池子)

getPair(TOKEN, WBNB) → 0xAAA...
getPair(WBNB, TOKEN) → 0xAAA... ← 相同!顺序已归一化

5. LP 代币 — 所有权凭证

5.1 LP 代币机制

当你添加流动性时,交易对合约会向你铸造 LP 代币。LP 代币是标准 ERC-20 代币,代表你对池子储备的比例性所有权。

LP 代币铸造流程 用户钱包 1,000 TOKEN 10 WETH 转入 代币 交易对合约 转入前: reserve0: 9,000 TOKEN reserve1: 90 WETH totalSupply: 900 LP 转入后: reserve0: 10,000 TOKEN reserve1: 100 WETH totalSupply: 1,000 LP 铸造 100 LP 用户获得 100 LP 代币 (10% 所有权) LP 代币铸造公式 首次铸造:LP = sqrt(amountA × amountB) − MINIMUM_LIQUIDITY 后续铸造:LP = min( amountA/reserveA, amountB/reserveB ) × totalSupply

5.2 初始流动性与最小锁定量

首次铸造时,Uniswap V2 会将少量 LP 代币(MINIMUM_LIQUIDITY = 1000 wei)发送到 address(0) 永久锁定。这确保池子的总供应量永远不会归零,防止除零错误和价格操纵攻击。

1
2
3
首次铸造:
发给提供者的 LP = sqrt(amount0 * amount1) - 1000
烧给 address(0) = 1000 ← 永久锁定

5.3 比例所有权计算

你的 LP 代币代表对池子储备的比例性权益

1
2
3
4
你的份额 %  = 你的LP / 总LP × 100

你的 TOKEN A = reserve0 × (你的LP / 总LP)
你的 TOKEN B = reserve1 × (你的LP / 总LP)

6. 添加流动性

6.1 首次添加 — 设定初始价格

首位流动性提供者通过选择存入代币的比例来设定池子的初始价格,这是一项关键责任:

若你存入 10,000 TOKEN 和 1 WETH,等于设定了 10,000 TOKEN/WETH 的价格。
若市场价为 500 TOKEN/WETH,套利者会立即从你的池子买入 WETH,造成无常损失。

最佳实践: 按当前市场价格比例存入。

6.2 后续添加 — 按比例匹配

池子创建后添加的所有流动性必须匹配当前储备比例(在你指定的滑点容忍度内)。路由合约计算最优金额:

1
2
3
4
目标:添加 amountADesired 的 TokenA
→ 所需 TokenB = amountADesired × reserveB / reserveA
→ 若你提供的 TokenB 多于所需,只使用 reserveB/reserveA 比例的部分
→ 多余部分退回你的钱包

6.3 多用户池子生命周期

池子生命周期:三位流动性提供者依次加入 ① Alice 创建池子 存入:1,000 TOKEN + 10 WETH 设定价格:100 TOKEN/WETH TOKEN: 1,000 WETH: 10 LP: 100(Alice: 100%) ② Bob 添加流动性 存入:500 TOKEN + 5 WETH 匹配现有比例 TOKEN: 1,500 WETH: 15 LP: 150(A 67%, B 33%) ③ Carol 添加流动性 存入:1,500 TOKEN + 15 WETH 匹配现有比例 TOKEN: 3,000 WETH: 30 LP: 300(A 33% B 17% C 50%) 三位用户使用同一个池子合约 最终共享池子状态 所有权 Alice: 100 LP(33.3%) Bob: 50 LP(16.7%) Carol: 150 LP(50%) 总计: 300 LP TOKEN 储备: 3,000 WETH 储备: 30 价格: 100 TOKEN/WETH k = 3,000 × 30 = 90,000 手续费率: 0.3% 每位用户的代币混合在池子中,只有 LP 代币追踪个人所有权。

7. 兑换 — 交易如何执行

7.1 兑换执行流程

代币兑换流程 ① 用户 调用路由合约 swapExactTokens ForTokens() ② 路由合约 检查 amountOutMin 验证截止时间 转入代币 → 交易对 ③ 交易对合约 检测收到的代币 计算输出金额 更新储备量 ④ 用户收到代币 输出代币转至用户 地址 ≥ amountOutMin ✓ 交易对合约计算(含 0.3% 手续费) 已知:amountIn = 10 WETH,reserveIn = 100 WETH,reserveOut = 10,000 TOKEN 步骤 1: amountInWithFee = amountIn × 997 → 10 × 997 = 9,970 步骤 2: numerator = 9,970 × 10,000 = 99,700,000 步骤 3: denominator = 100,000 + 9,970 = 109,970 结果:amountOut ≈ 906.6 TOKEN(即时价格为 1,000,价格影响约 9.3%)

7.2 手续费收取(0.3%)

Uniswap V2 每笔兑换收取 0.3% 手续费。这笔费用不会转给任何人 — 它留在池子储备中,每笔交易后略微增加 k 值。

1
2
每次兑换:new_k = (reserveIn + amountIn × 0.997) × (reserveOut − amountOut)
new_k > 原始 k ← 每笔交易后池子增长

LP 代币持有者受益于每个 LP 代币背书的储备量随时间增加。无需单独领取手续费 — 手续费自动复利进入储备比例。

7.3 最小输出与截止时间

路由合约强制执行两项关键保护:

参数 用途
amountOutMin 你能接受的最少代币(滑点保护)
deadline 超过此 Unix 时间戳后交易回滚(防止滞留交易在不利价格下执行)
1
2
3
// 典型滑点:0.5%
const amountOutMin = expectedOut * 995n / 1000n; // 接受最多 0.5% 滑点
const deadline = Math.floor(Date.now() / 1000) + 1200; // 20 分钟

8. 移除流动性

移除流动性会销毁你的 LP 代币,按比例返还你在当前储备中的份额:

移除流动性流程 移除前池子状态 TOKEN: 10,000 WETH: 100 总 LP: 1,000 你的 LP: 100(10%) 你的份额: 1,000 T + 10 W 销毁 100 LP Pair.burn() 1. 销毁 100 LP 代币 2. 计算比例份额 3. 向你转出代币 4. 更新储备 你收到 ~1,000 TOKEN ~10 WETH (+ 累积手续费) 金额可能与存入时 不同 移除后池子:TOKEN: 9,000 | WETH: 90 | 总 LP: 900 池子继续存在。其他 LP 现在拥有更高百分比。池子不会被删除。 注:收到金额反映当前储备,不是原始存入金额(可能有无常损失/收益)。

重要: 提取时收到的代币基于当前储备,而非原始存入金额。若期间发生大量交易,取回的代币比例会与存入时不同 — 这正是无常损失的本质。


9. 无常损失 — 隐性风险

9.1 什么是无常损失?

无常损失(Impermanent Loss,IL) 的成因是 AMM 公式在价格变化时自动再平衡池子。当一种代币价格相对另一种上涨时,AMM 自动卖出升值代币、买入贬值代币。这意味着 LP 持有的升值代币减少、贬值代币增多 — 与简单持有相比。

若价格恢复到原始比例,损失消失(因此称为"无常")。但若价格未恢复,在提取流动性时损失就会实现。

9.2 无常损失公式

r = 价格变化比率(最终价格 / 初始价格):

1
IL(r) = 2√r / (1 + r) − 1
无常损失 vs 价格变化 0% -1.2% -3.8% -5.7% -11.1% -20% 1x 1.25x 1.5x 2x 3x 4x 5x 10x 价格变化倍数(r) 无常损失 % 持币不动(0% 损失) 2x: -5.7% IL 4x: -20% IL 10x: -42.5% IL 价格下跌适用相同曲线(r = 1/原始值) 例:价格跌至 0.5x → 等同于 2x 上涨 → -5.7% IL
价格变化 无常损失
±0%(无变化) 0.00%
±25% −0.6%
±50% −2.0%
±2x(翻倍或减半) −5.7%
±3x −13.4%
±4x −20.0%
±5x −25.5%
±10x −42.5%

9.3 无常损失 vs 手续费收益

无常损失只是等式的一侧。LP 每次兑换可获得 0.3% 手续费。LP 是否盈利取决于哪一方更大:

1
2
3
4
5
6
7
8
9
LP 净收益 = 手续费收入 − 无常损失

盈利条件:
交易量高(兑换频繁)→ 手续费收入高
价格波动小 → IL 低

风险条件:
交易量低 → 手续费少
高波动性 → IL 大

稳定币对(如 USDC/USDT)IL 极低,但手续费收入也相对较少。高波动对手续费潜力更高,但 IL 风险也更大。


10. 手续费累积与 LP 收益

手续费在池子储备内部累积。移除流动性时,你收到当前增长后储备的比例份额:

手续费随时间累积 T0 创建池子 k = 100,000 兑换 #1 0.3% 手续费 k 增长 兑换 #2 0.3% 手续费 k 增长 兑换 #N 手续费复利 k = 101,200 提取 收到更多代币 比存入时更多! k 随每次兑换增加(手续费自动复利) 每个 LP 代币的储备背书随手续费增加。无需单独领取。 年化手续费率 ≈ (日交易量 / 池子 TVL) × 0.3% × 365

11. 同一代币对的多个池子

11.1 跨 DEX 套利

当同一代币对存在于多个 DEX 时,价格差异产生套利机会

跨 DEX 套利:价格均衡化 DEX 甲(PancakeSwap) Pool: 0xAAA... TOKEN: 10,000 WBNB: 100 价格:100 TOKEN/WBNB ← TOKEN 在此更便宜 DEX 乙(ApeSwap) Pool: 0xBBB...(不同!) TOKEN: 4,500 WBNB: 55 价格:81.8 TOKEN/WBNB ← TOKEN 在此更贵 套利者 在 DEX 甲买入 TOKEN 在 DEX 乙卖出 TOKEN 套利效应:价格趋于一致 DEX 甲 TOKEN 被买走 → 减少 WBNB 增加 价格上涨 ↑ DEX 乙 TOKEN 被卖入 → 增加 WBNB 减少 价格下跌 ↓ → 价格均衡 ← 套利持续直到价格一致(扣除手续费和 Gas 后) 两个池子的 LP 都从套利交易中获得手续费

11.2 多 DEX 流动性策略

你可以同时在多个 DEX 的同一代币对持仓,每个仓位独立:

池子 DEX 你的 LP 你的份额 备注
TOKEN/WBNB PancakeSwap V2 100 LP 5.0% 交易量更高
TOKEN/WBNB ApeSwap 50 LP 3.2% TVL 较低

优势: 从多个 DEX 获取手续费,降低单一 DEX 风险
注意事项: 需要更多资金,管理更复杂


12. Uniswap V2 vs V3 核心差异

CCBus 目标是 Uniswap V2 兼容 DEX。以下是 V3 的主要差异:

V2 全范围流动性 vs V3 集中流动性 Uniswap V2 / PancakeSwap V2 流动性均匀分布在整个价格范围 0 流动性分布于全价格范围 当前价格 ✓ 简单 — 存入后无需管理 ✓ 每个代币对一个池子 ✓ Gas 低,复杂度低 ✗ 资本效率较低 ✗ 大部分流动性处于极端价位处于闲置 → CCBus 支持 Uniswap V3 集中流动性,指定价格区间 $0 $∞ 价格 LP1 LP2 LP3 ✓ 最高 4000x 资本效率 ✓ 多手续费档位(0.01%–1%) ✗ 更复杂,Gas 更高 ✗ 需要主动管理 ✗ 同一代币对多个池子(按手续费档位) → CCBus 暂不支持
特性 V2 V3
流动性类型 全范围 集中区间
每对代币池子数 每工厂一个 多个(按手续费档位)
LP 复杂度 简单 需要管理价格区间
资本效率 基准 最高 4,000x
手续费档位 0.3% 固定 0.01%、0.05%、0.3%、1%
仓位格式 ERC-20 LP 代币 ERC-721 NFT
CCBus 支持 路线图中

13. 常见误解

❌ "我创建了池子所以是我的"
✅ 你是第一位 LP。其他人可以添加流动性。你拥有共享池子的 X%。

❌ "我的钱包地址决定使用哪个池子"
✅ 池子地址由(token0, token1, factory)决定,与钱包无关。

❌ "移除我的流动性会删除池子"
✅ 只要还有任意 LP 代币存在,池子就持续存在。其他用户不受你退出影响。

❌ "提取时我会收到和存入时一样的代币"
✅ 你收到当前储备的比例份额,受手续费和无常损失调整。

❌ "每次创建池子地址都是随机的"
✅ CREATE2 使池子地址具有确定性,可从(token0, token1, factory, initCodeHash)预先计算。

❌ "手续费需要单独领取"
✅ 手续费直接在池子储备中累积,移除流动性时自动实现。


14. 最佳实践

流动性提供者

  1. 先检查池子是否存在 — 任何操作前先调用 factory.getPair(tokenA, tokenB)
  2. 验证池子地址 — 通过工厂核验,不要相信用户提供的地址
  3. 按市场价格存入 — 创建新池子时按外部市场价格确定比例,避免即时套利损失
  4. 设置合适的滑点 — 稳定币对 0.5–1%,高波动对 1–3%
  5. 设置截止时间 — 始终使用截止时间(20–30 分钟),防止滞留交易在不利价格下执行
  6. 在大仓位前了解 IL — 根据代币对的历史波动率评估无常损失敞口
  7. 监控你的 LP 份额 — 随着其他人添加/移除流动性,你的百分比会变化

开发者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ 操作前始终检查池子是否存在
const pairAddress = await publicClient.readContract({
address: factoryAddress,
abi: FACTORY_ABI,
functionName: 'getPair',
args: [tokenA, tokenB]
});

const poolExists = pairAddress !== '0x0000000000000000000000000000000000000000';

if (!poolExists) {
// 告知用户:他们将设定初始价格
// 警告:仔细对照市场价格
}
1
2
3
4
5
// ✅ 始终通过工厂验证池子地址
const expectedPair = await factory.getPair(tokenA, tokenB);
if (userInput.toLowerCase() !== expectedPair.toLowerCase()) {
throw new Error('池子地址不匹配 — 可能是欺诈合约');
}
1
2
3
// ✅ 始终包含滑点和截止时间
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200); // 20 分钟
const amountOutMin = (expectedOutput * 995n) / 1000n; // 0.5% 滑点
1
2
3
4
// ✅ 正确计算所有权百分比
const lpBalance = await pair.balanceOf(userAddress);
const totalSupply = await pair.totalSupply();
const ownershipPct = (Number(lpBalance) * 100) / Number(totalSupply);

15. 常见问题

Q:我能创建私有流动性池吗?

标准 Uniswap V2 风格 DEX 的池子完全公开 — 任何人都可以添加流动性、移除流动性或交易。私有/有权限的池子需要自定义工厂合约。CCBus 基于公开 DEX 基础设施运行。

Q:两笔交易同时尝试创建相同池子会怎样?

CREATE2 确保地址具有确定性。第一笔交易部署合约;第二笔会回滚,因为该地址已有合约。标准路由合约先调用 getPair 确认,仅在需要时才调用 createPair

Q:我的 LP 代币百分比会随时间变化吗?

是的:

  • 其他人添加流动性 → 你的份额减少(稀释)
  • 其他人移除流动性 → 你的份额增加
  • 你添加 → 你的份额增加
  • 你移除 → 你的份额减少
  • 发生兑换 → 你的份额不变(但每个 LP 代币背书的储备因手续费增加)

Q:我能转移 LP 代币吗?

可以。LP 代币是标准 ERC-20 代币,可以转移、出售或质押在收益耕作合约中。持有 LP 代币的人可以赎回池子储备。

Q:池子中永久锁定的最低流动性是多少?

MINIMUM_LIQUIDITY = 1000(以 LP 代币最小单位计)在首次铸造时发送给 address(0),永远无法赎回。这防止池子被完全耗尽,并防范某些价格操纵攻击。

Q:如何从储备量计算当前价格?

1
2
3
4
5
6
const [reserve0, reserve1] = await pair.getReserves();
// token0 < token1(按地址排序)
// token0 以 token1 计的价格:
const priceToken0 = Number(reserve1) / Number(reserve0);
// token1 以 token0 计的价格:
const priceToken1 = Number(reserve0) / Number(reserve1);

Q:为什么 addLiquidity 有时返回的代币比我发送的少?

路由合约强制执行比例匹配。如果你提供的金额与当前储备比例不完全匹配,一侧会被全额使用,另一侧的多余部分退回你的钱包。amountAMin / amountBMin 参数保护你免受退款金额中过大滑点的影响。

Q:我能直接用 ETH(原生 BNB)进行流动性操作吗?

可以,通过路由合约的 addLiquidityETH / removeLiquidityETH。路由合约自动将你的原生代币(BNB → WBNB,ETH → WETH)包装后再与池子交互,池子始终持有包装版本。


延伸阅读