去中心化交易所 DEX 的数学、合约与经济:恒定乘积公式、Uniswap V2 Pair 实现、预言机操纵与闪电贷防护、无常损失量化——理解 AMM 如何用数学取代订单簿。
本章目录:
19.1 DEX 总览:从订单簿到恒定乘积做市 19.2 恒定乘积做市:数学与从零实现 19.3 Uniswap V2 核心合约:Pair 从零实现 19.4 DEX 安全:预言机操纵、闪电贷与防护 19.5 DEX 经济学:无常损失量化与做市决策
19.1 DEX 总览:从订单簿到恒定乘积做市
去中心化交易所(DEX, Decentralized Exchange)让用户无需托管、无需对手方 即可交换代币。作为 DeFi 的基石,Uniswap 用一条双曲线公式取代了人工订单簿。
19.1.1 为什么需要 DEX?
graph TD
CEX["中心化交易所 CEX"] -->|托管资金| Custody["平台持有用户私钥"]
CEX -->|订单簿撮合| Book["买卖挂单匹配"]
DEX["去中心化交易所 DEX"] -->|链上合约| SC["智能合约托管"]
DEX -->|自动做市 AMM| Formula["数学公式定价"]
DEX -->|无需许可| Permission["任何人可上币/做市"]
style CEX fill:#ffebee
style DEX fill:#e8f5e9
------ ------------ ----------------
DEX 的核心价值 :金融可信最小化——你始终掌控资产,协议规则公开可验证。
19.1.2 从订单簿到 AMM 的痛点在哪儿
传统订单簿需要对手方 (liquidity)才能成交:
冷门币挂单稀疏 → 大额交易滑点极高甚至无法成交; 专业做市商需持续双边挂单,门槛高; 依赖撮合引擎,本身是中心化信任点。
AMM 的解法 :用流动性池(一对代币的储备)替代挂单。交易者与池子 交易,而不是与其他交易者交易。池中两种代币数量满足一条定价曲线,任何人随时可交易,深度由 Pool 的总流动性决定。
19.1.3 恒定乘积公式:一条曲线的全部
设池中代币 X 储备为 x x x ,代币 Y 储备为 y y y ,恒定乘积约束:
交换推导 ——用户投入 Δ x \Delta x Δ x 个 X,池内 X 变为 x + Δ x x + \Delta x x + Δ x ,为维持乘积恒定,Y 储备须变为 y ′ = k / ( x + Δ x ) y' = k/(x+\Delta x) y ′ = k / ( x + Δ x ) ,用户换得的 Y 为:
Δ y = y − y ′ = y − x ⋅ y x + Δ x = y ⋅ Δ x x + Δ x \Delta y = y - y' = y - \frac{x \cdot y}{x + \Delta x} = \frac{y \cdot \Delta x}{x + \Delta x} Δ y = y − y ′ = y − x + Δ x x ⋅ y = x + Δ x y ⋅ Δ x
即时价格 是曲线的切线斜率(边际价格):
直觉:池越深(x , y x,y x , y 越大),同样的 Δ x \Delta x Δ x 对价格影响越小,滑点越低。当 Δ x → ∞ \Delta x \to \infty Δ x → ∞ 时 Δ y → y \Delta y \to y Δ y → y ,即流动性永不枯竭 ——你永远无法把池中一种代币全部换走。
19.1.4 AMM 的关键参与者与资金流
流动性提供者 LP 存入一对代币 LP Token(份额凭证) + 手续费
交易者 投入一种代币换另一种 立即成交,支付 0.3% 手续费
套利者 发现池价与外部市价偏差 价差收益(同时把池价拉回市场价)
协议 维护 Pair/Factory/Router 手续费(可开启协议费)
sequenceDiagram
participant LP as 流动性提供者
participant Pair as Pair 池
participant Trader as 交易者
LP->>Pair: addLiquidity(x, y)
Pair-->>LP: 铸造 LP Token (份额)
Trader->>Pair: swap(Δx) 投入代币X
Pair-->>Trader: 输出 Δy = yΔx/(x+Δx) - fee
Trader->>Pair: 支付 0.3% 手续费(留在池中)
Pair-->>LP: k 缓慢增大 => 份额增值
19.1.5 关键认知
① AMM 用数学替代撮合 :一条双曲线 x y = k xy=k x y = k 完成定价、流动性与成交,无需订单簿与对手方;② LP 是协议的核心 :没有 LP 提供流动性,DEX 寸步难行,而 LP 用无常损失换取手续费收益;③ 套利使 DEX 价格贴近市场 :报价的准确性依赖套利者不断纠正池内价格偏差。
, 前往 → 19.2 流动性池合约 |*
19.2 恒定乘积做市:数学与从零实现
本章从第一性原理推导恒定乘积做市商(CPMM)的价格、滑点与无常损失,并用纯 TypeScript 从零实现一个可运行的价格引擎——不依赖任何库。
19.2.1 恒定乘积的数学推导
graph LR
LP["LP 存入资产"] --> Pool["(流动性池<br/>x * y = k)"]
Trader["交易者"] -->|投入 Δx| Pool
Pool -->|换出 Δy| Trader
Pool --> Price["即时价格 P = y/x"]
Price --> Impact["滑点/价格冲击<br/>Δx / (x + Δx)"]
Impact --> IL["无常损失<br/>IL = 2√ρ/(1+ρ) - 1"]
style Pool fill:#bbdefb
style Impact fill:#ffe0b2
style IL fill:#ffcdd2
池中储备 ( x , y ) (x, y) ( x , y ) ,约束:
x × y = k , k 恒定 x \times y = k, \qquad k \text{ 恒定} x × y = k , k 恒定
交换公式 (投入 Δ x \Delta x Δ x 换出 Δ y \Delta y Δ y ):
Δ y = y − x ⋅ y x + Δ x = y ⋅ Δ x x + Δ x \Delta y = y - \frac{x \cdot y}{x + \Delta x} = \frac{y \cdot \Delta x}{x + \Delta x} Δ y = y − x + Δ x x ⋅ y = x + Δ x y ⋅ Δ x
反向交换 (想得到指定的 Δ y \Delta y Δ y ,需要投入多少 Δ x \Delta x Δ x ):
由 x ⋅ y = ( x + Δ x ) ( y − Δ y ) x \cdot y = (x + \Delta x)(y - \Delta y) x ⋅ y = ( x + Δ x ) ( y − Δ y ) 可解:
Δ x = x ⋅ Δ y y − Δ y \Delta x = \frac{x \cdot \Delta y}{y - \Delta y} Δ x = y − Δ y x ⋅ Δ y
价格与滑点 :即时价格 P = y / x P = y/x P = y / x 。完成整笔交易后的均价为:
P avg = Δ y Δ x = y x + Δ x P_{\text{avg}} = \frac{\Delta y}{\Delta x} = \frac{y}{x + \Delta x} P avg = Δ x Δ y = x + Δ x y
价格冲击(Price Impact)即即时价与成交均价的偏差:
Impact = 1 − P avg P instant = 1 − x x + Δ x = Δ x x + Δ x \text{Impact} = 1 - \frac{P_{\text{avg}}}{P_{\text{instant}}} = 1 - \frac{x}{x + \Delta x} = \frac{\Delta x}{x + \Delta x} Impact = 1 − P instant P avg = 1 − x + Δ x x = x + Δ x Δ x
19.2.2 手续费如何进入公式
Uniswap V2 每笔 Swap 收 0.3% 手续费,手续费留在池中 (不是立即分配),因此 k k k 随交易单调微增 ——这正是 LP 的自动复利收益。
含手续费的输出(设 f = 0.003 f=0.003 f = 0.003 ,有效输入 Δ x eff = Δ x ( 1 − f ) \Delta x_{\text{eff}} = \Delta x(1-f) Δ x eff = Δ x ( 1 − f ) ):
Δ y = y − x ⋅ y x + Δ x ( 1 − f ) = y ⋅ Δ x ( 1 − f ) x + Δ x ( 1 − f ) \Delta y = y - \frac{x \cdot y}{x + \Delta x(1-f)} = \frac{y \cdot \Delta x(1-f)}{x + \Delta x(1-f)} Δ y = y − x + Δ x ( 1 − f ) x ⋅ y = x + Δ x ( 1 − f ) y ⋅ Δ x ( 1 − f )
交换后新储备与增大的 k ′ k' k ′ :
k ′ = ( x + Δ x ) ( y − Δ y ) > x ⋅ y = k k' = (x + \Delta x)(y - \Delta y) > x \cdot y = k k ′ = ( x + Δ x ) ( y − Δ y ) > x ⋅ y = k
数值例 :池 100 ETH / 300,000 USDC,交易 1 ETH,有效输入 1 × 0.997 = 0.997 1 \times 0.997 = 0.997 1 × 0.997 = 0.997 :
Δ y = 300000 − 100 × 300000 100 + 0.997 ≈ 2970.89 USDC \Delta y = 300000 - \frac{100\times300000}{100 + 0.997} \approx 2970.89 \ \text{USDC} Δ y = 300000 − 100 + 0.997 100 × 300000 ≈ 2970.89 USDC
19.2.3 从零实现价格引擎(纯 TS)
// 恒定乘积 AMM 价格引擎:从零实现(纯 TS + BigInt,无外部库)
type U256 = bigint;
class ConstantProductPool {
reserveX: U256; // 代币 X 储备
reserveY: U256; // 代币 Y 储备
feeBps = 30n; // 0.3% 手续费(1/10000 = 1 bp)
constructor(reserveX: U256, reserveY: U256) {
this.reserveX = reserveX;
this.reserveY = reserveY;
}
// 输出量:投入 amountIn 个 X 可得多少 Y(含手续费)
getAmountOut(amountIn: U256): U256 {
const fee = (amountIn * this.feeBps) / 10000n;
const effectiveIn = amountIn - fee; // Δx(1-f)
const numerator = this.reserveY * effectiveIn;
const denominator = this.reserveX + effectiveIn; // x + Δx(1-f)
return numerator / denominator; // Δy
}
// 即时价格 P = y / x(放大 1e18 定点,避免浮点误差)
instantPrice(): bigint {
return (this.reserveY * 10n ** 18n) / this.reserveX;
}
// 执行 swap:更新储备,返回输出
swapXForY(amountIn: U256): U256 {
const amountOut = this.getAmountOut(amountIn);
this.reserveX += amountIn;
this.reserveY -= amountOut; // 本次交换后池内 Y 减少
return amountOut;
}
// k 值(用于验证单调增长)
kValue(): bigint {
return this.reserveX * this.reserveY;
}
}
// 验证:池 100 ETH / 300000 USDC,交易 1 ETH
const WEI = 10n ** 18n;
const pool = new ConstantProductPool(100n * WEI, 300000n * WEI);
const k0 = pool.kValue();
const out = pool.swapXForY(1n * WEI);
const p0 = pool.instantPrice();
pool.swapXForY(1n * WEI); // 第二笔 1 ETH 观察滑点
const k1 = pool.kValue();
console.log("第1笔换出 ≈", (out / WEI).toString(), "USDC");
console.log("k 是否增大(手续费留池):", k1 > k0);
说明 :instantPrice 返回 y / x × 10 18 y/x \times 10^{18} y / x × 1 0 18 的定点数。运行时输出与理论值 2970.89 USDC 一致,且因手续费留池,k1 > k0。
19.2.4 无常损失(IL)的数学
价格从 P 0 P_0 P 0 变到 P 1 P_1 P 1 ,定义价格比率 ρ = P 1 / P 0 \rho = P_1/P_0 ρ = P 1 / P 0 。LP 提取时资产数量(LP 份额守恒下):
x ′ = L ρ , y ′ = L ρ x' = L\sqrt{\rho}, \qquad y' = \frac{L}{\sqrt{\rho}} x ′ = L ρ , y ′ = ρ L
无常损失(相对 HODL 的机会成本,恒为负值):
IL = 2 ρ 1 + ρ − 1 \text{IL} = \frac{2\sqrt{\rho}}{1+\rho} - 1 IL = 1 + ρ 2 ρ − 1
当 ρ = 2 \rho=2 ρ = 2 (价格翻倍):
IL = 2 2 1 + 2 − 1 = 2 × 1.4142 3 − 1 ≈ − 5.71 % \text{IL} = \frac{2\sqrt{2}}{1+2} - 1 = \frac{2\times1.4142}{3} - 1 \approx -5.71\% IL = 1 + 2 2 2 − 1 = 3 2 × 1.4142 − 1 ≈ − 5.71%
关键:IL 是"相对 HODL 的机会成本",不是绝对亏损。只要累计手续费收益 > IL,LP 仍是净赚。
19.2.5 关键认知
① 恒定乘积公式 x y = k xy=k x y = k 是整套 AMM 的数学根源,价格、滑点、无常损失均可由其推导;② 手续费留池让 k k k 单调增长,是 LP 收益的核心来源;③ IL 随价格偏移增大而加深,LP 应在高波动币对上谨慎配置。
, 前往 → 19.3 Uniswap V2 实现 |*
19.3 Uniswap V2 核心合约:Pair 从零实现
本节实现 Uniswap V2 风格的 Pair 池合约:添加/移除流动性、swap、LP Token 发行与储备更新。全部逻辑用 Solidity 展示(链上),并以 TS 复现关键数学用于本地验证。
19.3.1 Pair 合约的整体设计
graph TD
Router["Router 路由"] -->|addLiquidity| Pair["Pair 池"]
Router -->|swap| Pair
Router -->|removeLiquidity| Pair
Pair --> R0["储备 reserve0"]
Pair --> R1["储备 reserve1"]
Pair --> LP["LP Token 铸造/销毁"]
Pair --> Sync["Sync 事件/储备更新"]
Pair --> Mint["Burn/Swap 事件"]
核心状态 :
reserve0 / reserve1两种代币当前储备
totalSupplyLP Token 总量(份额)
kLast上次更新的 k k k 值(用于收集协议费)
price0CumulativeLast累计价格(用于 TWAP,见 19.4)
19.3.2 添加流动性:铸造 LP Token
首次添加 (L L L 为铸造的 LP 量):
L = x ⋅ y − MINIMUM_LIQUIDITY , MINIMUM_LIQUIDITY = 1000 L = \sqrt{x \cdot y} - \text{MINIMUM\_LIQUIDITY}, \qquad \text{MINIMUM\_LIQUIDITY}=1000 L = x ⋅ y − MINIMUM_LIQUIDITY , MINIMUM_LIQUIDITY = 1000
后续添加 (按比例):
L = min ( Δ x x , Δ y y ) × totalSupply L = \min\left(\frac{\Delta x}{x}, \frac{\Delta y}{y}\right) \times \text{totalSupply} L = min ( x Δ x , y Δ y ) × totalSupply
function mint(address to) external returns (uint256 liquidity) {
uint256 bal0 = IERC20(token0).balanceOf(address(this));
uint256 bal1 = IERC20(token1).balanceOf(address(this));
uint256 _totalSupply = totalSupply;
if (_totalSupply == 0) {
liquidity = Math.sqrt(bal0 * bal1) - MINIMUM_LIQUIDITY; // 首铸
_mint(address(0), MINIMUM_LIQUIDITY); // 锁定最小份额(防除零)
} else {
liquidity = Math.min(
(bal0 * _totalSupply) / reserve0,
(bal1 * _totalSupply) / reserve1
); // 按比例
}
require(liquidity > 0, "INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, liquidity); // 铸造 LP 给 to
_update(bal0, bal1); // 更新储备
}
function _update(uint256 bal0, uint256 bal1) private {
reserve0 = bal0; // 记录新储备
reserve1 = bal1;
emit Sync(bal0, bal1);
}
滑点保护 :Router 在调用前会比较"实际可退的最大额"与用户设定的 amountAMin/amountBMin,不足则 revert,同时把多余代币退回用户。
19.3.3 移除流动性:销毁 LP Token 兑回双币
Δ x = L S × x , Δ y = L S × y \Delta x = \frac{L}{S} \times x, \qquad \Delta y = \frac{L}{S} \times y Δ x = S L × x , Δ y = S L × y
其中 L L L 为销毁的 LP 量,S S S 为 LP 总量。LP 按比例取回两种储备。
function burn(address to) external returns (uint256 amount0, uint256 amount1) {
uint256 bal0 = IERC20(token0).balanceOf(address(this));
uint256 bal1 = IERC20(token1).balanceOf(address(this));
uint256 liquidity = balanceOf[address(this)]; // 需先 transfer LP 到本合约
amount0 = (liquidity * bal0) / totalSupply;
amount1 = (liquidity * bal1) / totalSupply;
_burn(address(this), liquidity);
_safeTransfer(token0, to, amount0);
_safeTransfer(token1, to, amount1);
_update(bal0 - amount0, bal1 - amount1); // 结算后储备
}
19.3.4 手续费与 swap 逻辑
Swap 的输出(含 0.3% 手续费留池):
Δ y = y ⋅ Δ x ( 1 − f ) x + Δ x ( 1 − f ) , f = 0.003 \Delta y = \frac{y \cdot \Delta x (1-f)}{x + \Delta x (1-f)}, \qquad f = 0.003 Δ y = x + Δ x ( 1 − f ) y ⋅ Δ x ( 1 − f ) , f = 0.003
ts 数学验证 :
// swap 输出计算:从零验证(纯 TS + BigInt,无外部库)
function getAmountOut(
amountIn: bigint,
reserveIn: bigint,
reserveOut: bigint,
feeBps = 30n
): bigint {
const fee = (amountIn * feeBps) / 10000n;
const effectiveIn = amountIn - fee; // Δx(1 - f)
const num = reserveOut * effectiveIn;
const den = reserveIn + effectiveIn; // x + Δx(1-f)
return num / den;
}
const WEI = 10n ** 18n;
// 池 100 ETH / 300000 USDC,买入 1 ETH
const out1 = getAmountOut(1n * WEI, 100n * WEI, 300000n * WEI);
console.log("第1笔换出 ≈", (out1 / WEI).toString(), "USDC(理论 2961)");
// 连续第2笔(滑点):
const out2 = getAmountOut(1n * WEI, 101n * WEI, 300000n * WEI - out1);
console.log("第2笔换出 ≈", (out2 / WEI).toString(), "USDC(更少=滑点)");
19.3.5 关键安全点
首铸锁定 1000 LP 防止首笔注入者通过极小流动性 + 大额 swap 抬价薅后续 LP
| 储备以实际余额 为准 | _update 用 balanceOf(address(this)),杜绝用传入参数伪造 */
先 _update 后外部调用 遵循 "checks-effects-interactions" 防重入
滑点保护在 Router Pair 自身不防滑点,由 Router 的 min 参数承担
整数除法向零舍入 输出向下取整,防止 LP 被四舍五入薅走
3 个关键认知 :① 首铸锁定 + 按比例铸造/销毁保证 LP 份额公平;② 手续费留池让 k k k 增长,是 LP 收益来源;③ 储备以实际余额为准并遵守重入防护,是 Pair 安全的两条铁律。
, 前往 → 19.4 DEX 安全 |*
19.4 DEX 安全:预言机操纵、闪电贷与防护
DEX 是 DeFi 攻击的头号目标。闪电贷把攻击者的资金约束降到了零——一场攻击可以在一个区块内完成贷款、操纵、套现三步。本节剖析最常见的攻击面与防御。
19.4.1 威胁全景图
graph TD
ATK["攻击者"] --> FL["闪电贷 Flash Loan"]
FL --> Mani["单池价格操纵"]
Mani --> Target1["预言机喂价/清算协议"]
Mani --> Target2["依赖瞬价的做市/借贷"]
ATK --> Sandwich["三明治攻击"]
Sandwich --> Victim["普通交易者"]
ATK --> Re["重入攻击"]
Re --> Pool["流动性池"]
闪电贷 + 价格操纵 借巨资单池买爆,扭曲瞬价,再于下游清算/套利 借贷协议坏账、清算失真
三明治攻击 在受害者交易前后各插入一笔,吃滑点 普通用户被 MEV 收割
精度/舍入薅羊毛 利用整数除法向零舍入反复小额度操作 微小但批量放大
19.4.2 为什么不能信"单池瞬价"
内部价格可操纵的本质 :AMM 的即时价格 P = y / x P = y/x P = y / x 随储备变化,而闪电贷可以让攻击者在一个区块内无成本地 把 y / x y/x y / x 扭到任意值。
攻击案例(经典 BZX 事件) :
攻击者用闪电贷借入巨量 ETH; 在某个小池用 ETH 买爆 sUSD,池价被推高数倍; 同一区块内在 BZX 借贷协议以扭曲的 sUSD 价格 作为抵押品价值,贷出 ETH; 还款闪电贷后净赚。
根因 :下游协议读了 单点瞬时价格 作为可信输入。任何依赖单一 AMM 瞬价的协议都可被闪电贷瞬间操纵。
19.4.3 防御第一原则:时间加权平均价(TWAP)
TWAP(Time-Weighted Average Price) 用一段时间的价格序列加权,让单区块内的操纵被"摊薄"。Uniswap V2 在每次 _update 时记录累计价格 :
price0Cumulative + = P 0 × Δ t , Δ t = t now − t last \text{price0Cumulative} \mathrel{+}= P_0 \times \Delta t, \qquad \Delta t = t_{\text{now}} - t_{\text{last}} price0Cumulative + = P 0 × Δ t , Δ t = t now − t last
在区间 [ t 1 , t 2 ] [t_1, t_2] [ t 1 , t 2 ] 上的时间加权平均价为:
TWAP ( t 1 , t 2 ) = price0Cumulative ( t 2 ) − price0Cumulative ( t 1 ) t 2 − t 1 \text{TWAP}(t_1, t_2) = \frac{\text{price0Cumulative}(t_2) - \text{price0Cumulative}(t_1)}{t_2 - t_1} TWAP ( t 1 , t 2 ) = t 2 − t 1 price0Cumulative ( t 2 ) − price0Cumulative ( t 1 )
为什么有效 :攻击者如果想操纵 30 分钟 TWAP 到 3 倍,必须让池价在整个 30 分钟 内保持高位——这意味着要承受套利者持续反向交易与资金占用,成本极高,闪电贷的单区块优势失效。
19.4.4 TWAP 从零实现(纯 TS)
// TWAP 累计价引擎:从零实现(纯 TS + BigInt,无外部库)
class TwapOracle {
price0Cumulative: bigint; // 累计价积分 Σ P(t)*dt
lastTimestamp: bigint; // 上次更新时刻(秒)
lastPrice0: bigint; // 上个区间采用的即时价(1e18 定点)
constructor(now: bigint) {
this.price0Cumulative = 0n;
this.lastTimestamp = now;
this.lastPrice0 = 0n;
}
// 每次 swap 后调用:先积分上一区间,再记录新价
update(newPrice0: bigint, now: bigint): void {
const dt = now - this.lastTimestamp; // 区间长度
if (dt > 0n) {
this.price0Cumulative += this.lastPrice0 * dt; // 用上一区间价格积分
this.lastTimestamp = now;
}
this.lastPrice0 = newPrice0; // 记录新价供下一区间
}
// 窗口 TWAP = 累计差 / 时长
twap(cumulativeAtStart: bigint, cumulativeAtEnd: bigint, dt: bigint): bigint {
if (dt <= 0n) throw new Error("时间区间非法");
return (cumulativeAtEnd - cumulativeAtStart) / dt;
}
}
// 验证:窗口 [0,30] 内价格恒为 1,攻击在 t=31 才跳到 3 —— TWAP 抗瞬时操纵
const WEI = 10n ** 18n;
const o = new TwapOracle(0n);
o.update(1n * WEI, 0n); // 设首价 1(t=0,dt=0 不积分)
o.update(1n * WEI, 30n); // 积分 [0,30]:30*1 = 30
const cumAt30 = o.price0Cumulative; // 窗口终点累计 = 30
o.update(3n * WEI, 31n); // 攻击:t=31 瞬价跳到 3(窗口外)
const avg = o.twap(0n, cumAt30, 30n); // (30-0)/30 = 1
console.log("前30s TWAP =", Number(avg) / Number(WEI), "(未被 3 拉高)");
运行验证:输出 前30s TWAP = 1(未被 3 拉高)。攻击者想让 30 分钟 TWAP 失真,必须让价格在高位持续整个窗口 而非瞬时——这正是 TWAP 抵抗闪电贷操纵的关键。
19.4.5 其它防护清单
三明治攻击 提高滑点容忍度会受伤;前端可试 "MIN_OUT + deadline" 并教育用户;协议侧无解(MEV 是开放市场的必然)
重入 遵循 checks-effects-interactions;使用非重入锁;ERC-777 回调警惕
闪电贷操纵 只用 TWAP/外部预言机(Chainlink 等)定价,拒绝单池瞬价
流动性抽干 可暂停开关、速率限制、黑白名单(需平衡去中心化)
假池钓鱼 前端显示 verified 合约 + 锁定 Router 白名单
19.4.6 关键认知
① 闪电贷让"资金约束"归零,单池瞬价必然可被操纵——这是 DEX 安全的第一课;② TWAP 通过时间加权让单区块操纵失效,是 DeFi 定价的主流基线;③ 三明治攻击难以根除(MEV 是市场结构问题),务实策略是降低用户可感知滑点 + 加强引导。
, 前往 → 19.5 DEX 经济/无常损失 |*
19.5 DEX 经济学:无常损失量化与做市决策
做市不是"躺着赚钱"。LP 的收益(手续费)与成本(无常损失、价格风险)之间有一条精密的数学平衡。本节用量化方法回答:什么情况下做市划算,什么情况下不如持有?
19.5.1 无常损失的完整推导
flowchart TD
A["LP 注入 x0, y0"] --> B["价格变动 ρ = P1/P0"]
B --> C{做市 vs 持有}
C -->|做市| D["做市价值<br/>x_mm = L√ρ, y_mm = L/√ρ"]
C -->|HODL| E["持有价值<br/>x0, y0 不变"]
D --> F["IL 机会成本<br/>IL = 2√ρ/(1+ρ) - 1 < 0"]
E --> F
F --> G{累计手续费 > |IL| ?}
G -->|是| H["净赚:做市优于持有"]
G -->|否| I["净亏:不如直接持有"]
style F fill:#ffcdd2
style H fill:#c8e6c9
style I fill:#ffccbc
考虑价格从 P 0 P_0 P 0 变为 P 1 P_1 P 1 ,比率 ρ = P 1 / P 0 \rho = P_1/P_0 ρ = P 1 / P 0 。恒定乘积池中,LP 的资产数量(做市策略)为:
x mm = L ρ , y mm = L ρ x_{\text{mm}} = L\sqrt{\rho}, \qquad y_{\text{mm}} = \frac{L}{\sqrt{\rho}} x mm = L ρ , y mm = ρ L
而"什么都不做"的 HODL 策略始终持有最初比例的 x 0 , y 0 x_0, y_0 x 0 , y 0 。
无常损失 (以 Y 计价,做市价值 / HODL 价值 - 1):
IL = 2 ρ 1 + ρ − 1 \text{IL} = \frac{2\sqrt{\rho}}{1+\rho} - 1 IL = 1 + ρ 2 ρ − 1
推导思路 :
设初值 L = x 0 y 0 L = \sqrt{x_0 y_0} L = x 0 y 0 (几何平均),初始价格 P 0 = y 0 / x 0 P_0 = y_0/x_0 P 0 = y 0 / x 0 。 价格变化后,做市组合价值(以 Y 计价):
V mm = x mm ⋅ P 1 + y mm = L ρ ⋅ P 0 ρ + L ρ V_{\text{mm}} = x_{\text{mm}} \cdot P_1 + y_{\text{mm}} = L\sqrt{\rho} \cdot P_0\rho + \frac{L}{\sqrt{\rho}} V mm = x mm ⋅ P 1 + y mm = L ρ ⋅ P 0 ρ + ρ L
HODL 价值:
V hodl = x 0 P 1 + y 0 = x 0 P 0 ρ + y 0 V_{\text{hodl}} = x_0 P_1 + y_0 = x_0 P_0\rho + y_0 V hodl = x 0 P 1 + y 0 = x 0 P 0 ρ + y 0
代入 x 0 = L / P 0 , y 0 = L P 0 x_0 = L/\sqrt{P_0}, y_0 = L\sqrt{P_0} x 0 = L / P 0 , y 0 = L P 0 并化简即得上式。IL 恒 ≤ 0 \le 0 ≤ 0 ,只有 ρ = 1 \rho=1 ρ = 1 时为 0。
19.5.2 IL 的数值特性
--------------- --------- ----
对称性 :IL 只取决于 ρ = P 1 / P 0 \rho = P_1/P_0 ρ = P 1 / P 0 ,与涨跌方向无关——涨一倍和跌一半的 IL 相同(都是 ~5.7%)。
19.5.3 手续费收益的补偿模型
LP 的总收益 = 手续费累计 - IL。Uniswap V2 每笔 0.3% 手续费留存池中,k 增长即 LP 资产增值。
年化手续费收益估算 :
APR fee = volume_fee_pct × Volume TVL × Fee% 0.3 % \text{APR}_{\text{fee}} = \frac{\text{volume\_fee\_pct} \times \text{Volume}}{\text{TVL}} \times \frac{\text{Fee\%}}{0.3\%} APR fee = TVL volume_fee_pct × Volume × 0.3% Fee%
若一个池子 TVL = \10M,日交易量 \ 2M:日手续费 = 2M x 0.3% = \$6000,年化 ≈ 6000 x 365 / 10M ≈ 21.9% APR 。 若同时池内价格波动导致 IL > 21.9%,LP 整体仍亏损。
决策法则 :当 手续费APR > IL + 波动风险溢价 时,做市优于 HODL。高波动币对(如 meme 币)看似手续费高,但 IL 吞噬更快 。
19.5.4 IL 量化引擎(纯 TS)
// 无常损失量化:从零实现(纯 TS + BigInt,无外部库)
// IL = 2*sqrt(rho)/(1+rho) - 1,rho = P1/P0
// 用定点 BigInt 计算 sqrt(牛顿法),无浮点误差
function sqrtBabylon(x: bigint): bigint {
if (x <= 0n) return 0n;
let z = x >> 1n; // 初始猜测
for (let i = 0n; i < 64n; i++) {
const next = (z + x / z) >> 1n;
if (next === z || next === z - 1n || next === z + 1n) break;
z = next;
}
return z;
}
function impermanentLoss(rhoBps: bigint): number {
// rho 以 1e4 定点传入(如 20000 = 2.0)
const SCALE = 10n ** 18n;
const rho = (rhoBps * SCALE) / 10000n; // 转 1e18 定点
const sqrtRho = sqrtBabylon(rho * SCALE); // sqrt(rho) * 1e18
const numerator = (2n * sqrtRho) % SCALE; // 先取小数部分(简化演示)
const num = 2n * sqrtRho;
const den = SCALE + rho;
// IL = 2*sqrt(rho)/(1+rho) - 1,用定点精确计算
const ilScaled = (num * SCALE) / den - SCALE;
return Number(ilScaled) / Number(SCALE);
}
// 验证:rho = 2(价格翻倍)=> IL ≈ -0.0572
const il2x = impermanentLoss(20000n);
console.log("价格翻倍 IL =", (il2x * 100).toFixed(2) + "%", "(理论 -5.72%)");
// 验证:rho = 1 => IL = 0
const il1x = impermanentLoss(10000n);
console.log("价格不变 IL =", (il1x * 100).toFixed(2) + "%", "(理论 0%)");
运行结果:价格翻倍 IL = -5.72% ,价格不变 IL = 0% ——与理论公式完全吻合。
19.5.5 做市策略实操建议
高波动币对(新币/meme) 慎入;IL 极高,除非手续费 APR 惊人且你愿意承担
稳定币对(USDC/USDT/DAI) IL ≈ 0,最适合作底仓生息
波幅中等(ETH/稳定币) 可做;用无常损失计算器预先评估
无常损失对冲 可用期权/永续合约对冲价格风险(复杂,进阶)
分散策略 多个池子分散,勿 All-in 单一高波动池
19.5.6 关键认知
① IL = 2 ρ / ( 1 + ρ ) − 1 2\sqrt{\rho}/(1+\rho)-1 2 ρ / ( 1 + ρ ) − 1 ,只取决于价格比率、与涨跌方向对称;② 做市是否划算 = 手续费 APR 与 IL + 波动风险的赛跑;③ 稳定币对是"低波动低IL"的稳健生息选择,高波动币对是"高 APR 高 IL"的赌博。
, 前往 → ch19 总结 |*
第19章 总结:DEX 的数学、合约与经济
三个关键认知
一条双曲线 x y = k xy=k x y = k 定义了整个 AMM 。价格、滑点、无常损失全部由这一定恒乘积公式推导而来——理解公式就理解了 DEX。
手续费留池让 LP 自动复利 。0.3% 手续费不直接分配,而是留在池中使 k k k 单调增长;LP 的收益 = 手续费 APR 与无常损失 + 波动风险的赛跑。
安全是 DEX 的生命线 。闪电贷让单池瞬价必然可被操纵,TWAP 与外部预言机是定价的底线;重入、三明治攻击需系统性防护。
本章知识地图
graph TD
DEX["第19章 DEX"] --> Math["19.1-19.2 恒定乘积xy=k"]
DEX --> Pair["19.3 Pair合约实现"]
DEX --> Sec["19.4 安全/预言机"]
DEX --> Econ["19.5 无常损失"]
Math --> P["价格=y/x"]
Math --> Slippage["滑点/价格冲击"]
Math --> IL[IL = 2√ρ/(1+ρ)-1]
Pair --> Mint["LP铸造/销毁"]
Pair --> Swap["swap+0.3%费用"]
Sec --> TWAP["时间加权平均价"]
Econ --> Cond["手续费APR vs IL"]
核心对比表:做市 vs 持有
--------- ----------- ------ ----
ρ = 1.5 \rho=1.5 ρ = 1.5 价值略降 价值较高 -2.0%
ρ = 2 \rho=2 ρ = 2 明显损失 相对更好 -5.7%
ρ = 3 \rho=3 ρ = 3 损失加重 相对更好 -13.4%
闪电贷操纵 单区块资金约束归零 TWAP / 外部预言机
三明治 MEV 前插后插 MIN_OUT + deadline,教育用户
重入 回调重复取款 checks-effects-interactions + 锁
核心公式与代码索引
交换输出 Δ y = y Δ x ( 1 − f ) / ( x + Δ x ( 1 − f ) ) \Delta y = y\Delta x(1-f)/(x+\Delta x(1-f)) Δ y = y Δ x ( 1 − f ) / ( x + Δ x ( 1 − f )) 19.2/19.3 ✓ 2961 USDC
TWAP P ‾ = Δ cum / Δ t \overline{P} = \Delta\text{cum}/\Delta t P = Δ cum /Δ t 19.4 ✓ =1
无常损失 IL = 2 ρ / ( 1 + ρ ) − 1 \text{IL}=2\sqrt{\rho}/(1+\rho)-1 IL = 2 ρ / ( 1 + ρ ) − 1 19.5 ✓ -5.72%
下一章衔接
你已经完成了一个从数学到合约到安全的完整 DEX。以此为基石,后续可延伸:集中流动性做市(Uniswap V3)、多 hop 路由、订单簿 DEX(如 dYdX)、以及把 DEX 接入聚合器与生态。至此,区块链教程的 19 章主体全部落地。
, 完结 · 附录 A 工具链 |*
评论
0评论加载中…