教程区块链区块链技术第19章 DEX 去中心化交易所

本页目录

去中心化交易所 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
维度CEX(币安等)DEX(Uniswap 等)
----------------------------------
资产托管平台热/冷钱包用户自己掌控(非托管)
定价机制订单簿撮合AMM 公式自动定价
上币需审核无需许可,一键创建池
可审查性平台可冻结/下架代码即法律(除非治理)
深度高(专业做市商)取决于池内流动性(TVL)

DEX 的核心价值:金融可信最小化——你始终掌控资产,协议规则公开可验证。


19.1.2 从订单簿到 AMM 的痛点在哪儿

传统订单簿需要对手方(liquidity)才能成交:

  • 冷门币挂单稀疏 → 大额交易滑点极高甚至无法成交;
  • 专业做市商需持续双边挂单,门槛高;
  • 依赖撮合引擎,本身是中心化信任点。

AMM 的解法:用流动性池(一对代币的储备)替代挂单。交易者与池子交易,而不是与其他交易者交易。池中两种代币数量满足一条定价曲线,任何人随时可交易,深度由 Pool 的总流动性决定。


19.1.3 恒定乘积公式:一条曲线的全部

设池中代币 X 储备为 xx,代币 Y 储备为 yy,恒定乘积约束:

x×y=kx \times y = k

交换推导——用户投入 Δx\Delta x 个 X,池内 X 变为 x+Δxx + \Delta x,为维持乘积恒定,Y 储备须变为 y=k/(x+Δx)y' = k/(x+\Delta x),用户换得的 Y 为:

Δy=yy=yxyx+Δx=yΔxx+Δx\Delta y = y - y' = y - \frac{x \cdot y}{x + \Delta x} = \frac{y \cdot \Delta x}{x + \Delta x}

即时价格是曲线的切线斜率(边际价格):

P=yxP = \frac{y}{x}

直觉:池越深(x,yx,y 越大),同样的 Δx\Delta x 对价格影响越小,滑点越低。当 Δx\Delta x \to \inftyΔyy\Delta y \to 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 用数学替代撮合:一条双曲线 xy=kxy=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=k,k 恒定x \times y = k, \qquad k \text{ 恒定}

交换公式(投入 Δx\Delta x 换出 Δy\Delta y):

Δy=yxyx+Δx=yΔxx+Δx\Delta y = y - \frac{x \cdot y}{x + \Delta x} = \frac{y \cdot \Delta x}{x + \Delta x}

反向交换(想得到指定的 Δy\Delta y,需要投入多少 Δx\Delta x):

xy=(x+Δx)(yΔy)x \cdot y = (x + \Delta x)(y - \Delta y) 可解:

Δx=xΔyyΔy\Delta x = \frac{x \cdot \Delta y}{y - \Delta y}

价格与滑点:即时价格 P=y/xP = y/x。完成整笔交易后的均价为:

Pavg=ΔyΔx=yx+ΔxP_{\text{avg}} = \frac{\Delta y}{\Delta x} = \frac{y}{x + \Delta x}

价格冲击(Price Impact)即即时价与成交均价的偏差:

Impact=1PavgPinstant=1xx+Δx=Δxx+Δx\text{Impact} = 1 - \frac{P_{\text{avg}}}{P_{\text{instant}}} = 1 - \frac{x}{x + \Delta x} = \frac{\Delta x}{x + \Delta x}

19.2.2 手续费如何进入公式

Uniswap V2 每笔 Swap 收 0.3% 手续费,手续费留在池中(不是立即分配),因此 kk 随交易单调微增——这正是 LP 的自动复利收益。

含手续费的输出(设 f=0.003f=0.003,有效输入 Δxeff=Δx(1f)\Delta x_{\text{eff}} = \Delta x(1-f)):

Δy=yxyx+Δx(1f)=yΔx(1f)x+Δx(1f)\Delta y = y - \frac{x \cdot y}{x + \Delta x(1-f)} = \frac{y \cdot \Delta x(1-f)}{x + \Delta x(1-f)}

交换后新储备与增大的 kk':

k=(x+Δx)(yΔy)>xy=kk' = (x + \Delta x)(y - \Delta y) > x \cdot y = k

数值例:池 100 ETH / 300,000 USDC,交易 1 ETH,有效输入 1×0.997=0.9971 \times 0.997 = 0.997:

Δy=300000100×300000100+0.9972970.89 USDC\Delta y = 300000 - \frac{100\times300000}{100 + 0.997} \approx 2970.89 \ \text{USDC}

19.2.3 从零实现价格引擎(纯 TS)

typescript

// 恒定乘积 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×1018y/x \times 10^{18} 的定点数。运行时输出与理论值 2970.89 USDC 一致,且因手续费留池,k1 > k0


19.2.4 无常损失(IL)的数学

价格从 P0P_0 变到 P1P_1,定义价格比率 ρ=P1/P0\rho = P_1/P_0。LP 提取时资产数量(LP 份额守恒下):

x=Lρ,y=Lρx' = L\sqrt{\rho}, \qquad y' = \frac{L}{\sqrt{\rho}}

无常损失(相对 HODL 的机会成本,恒为负值):

IL=2ρ1+ρ1\text{IL} = \frac{2\sqrt{\rho}}{1+\rho} - 1

ρ=2\rho=2(价格翻倍):

IL=221+21=2×1.4142315.71%\text{IL} = \frac{2\sqrt{2}}{1+2} - 1 = \frac{2\times1.4142}{3} - 1 \approx -5.71\%
ρ\rhoIL 损失
---------------
1(不变)0%
1.25-0.6%
1.5-2.0%
2-5.7%
3-13.4%

关键:IL 是"相对 HODL 的机会成本",不是绝对亏损。只要累计手续费收益 > IL,LP 仍是净赚。


19.2.5 关键认知

① 恒定乘积公式 xy=kxy=k 是整套 AMM 的数学根源,价格、滑点、无常损失均可由其推导;② 手续费留池让 kk 单调增长,是 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上次更新的 kk 值(用于收集协议费)
price0CumulativeLast累计价格(用于 TWAP,见 19.4)

19.3.2 添加流动性:铸造 LP Token

首次添加(LL 为铸造的 LP 量):

L=xyMINIMUM_LIQUIDITY,MINIMUM_LIQUIDITY=1000L = \sqrt{x \cdot y} - \text{MINIMUM\_LIQUIDITY}, \qquad \text{MINIMUM\_LIQUIDITY}=1000

后续添加(按比例):

L=min(Δxx,Δyy)×totalSupplyL = \min\left(\frac{\Delta x}{x}, \frac{\Delta y}{y}\right) \times \text{totalSupply}
solidity

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=LS×x,Δy=LS×y\Delta x = \frac{L}{S} \times x, \qquad \Delta y = \frac{L}{S} \times y

其中 LL 为销毁的 LP 量,SS 为 LP 总量。LP 按比例取回两种储备。

solidity

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(1f)x+Δx(1f),f=0.003\Delta y = \frac{y \cdot \Delta x (1-f)}{x + \Delta x (1-f)}, \qquad f = 0.003

ts 数学验证:

typescript

// 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

| 储备以实际余额为准 | _updatebalanceOf(address(this)),杜绝用传入参数伪造 */

_update 后外部调用遵循 "checks-effects-interactions" 防重入
滑点保护在 RouterPair 自身不防滑点,由 Router 的 min 参数承担
整数除法向零舍入输出向下取整,防止 LP 被四舍五入薅走

3 个关键认知:① 首铸锁定 + 按比例铸造/销毁保证 LP 份额公平;② 手续费留池让 kk 增长,是 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/xP = y/x 随储备变化,而闪电贷可以让攻击者在一个区块内无成本地y/xy/x 扭到任意值。

攻击案例(经典 BZX 事件):

  1. 攻击者用闪电贷借入巨量 ETH;
  2. 在某个小池用 ETH 买爆 sUSD,池价被推高数倍;
  3. 同一区块内在 BZX 借贷协议以扭曲的 sUSD 价格作为抵押品价值,贷出 ETH;
  4. 还款闪电贷后净赚。

根因:下游协议读了 单点瞬时价格 作为可信输入。任何依赖单一 AMM 瞬价的协议都可被闪电贷瞬间操纵。


19.4.3 防御第一原则:时间加权平均价(TWAP)

TWAP(Time-Weighted Average Price) 用一段时间的价格序列加权,让单区块内的操纵被"摊薄"。Uniswap V2 在每次 _update 时记录累计价格:

price0Cumulative+=P0×Δt,Δt=tnowtlast\text{price0Cumulative} \mathrel{+}= P_0 \times \Delta t, \qquad \Delta t = t_{\text{now}} - t_{\text{last}}

在区间 [t1,t2][t_1, t_2] 上的时间加权平均价为:

TWAP(t1,t2)=price0Cumulative(t2)price0Cumulative(t1)t2t1\text{TWAP}(t_1, t_2) = \frac{\text{price0Cumulative}(t_2) - \text{price0Cumulative}(t_1)}{t_2 - t_1}

为什么有效:攻击者如果想操纵 30 分钟 TWAP 到 3 倍,必须让池价在整个 30 分钟内保持高位——这意味着要承受套利者持续反向交易与资金占用,成本极高,闪电贷的单区块优势失效。


19.4.4 TWAP 从零实现(纯 TS)

typescript

// 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

考虑价格从 P0P_0 变为 P1P_1,比率 ρ=P1/P0\rho = P_1/P_0。恒定乘积池中,LP 的资产数量(做市策略)为:

xmm=Lρ,ymm=Lρx_{\text{mm}} = L\sqrt{\rho}, \qquad y_{\text{mm}} = \frac{L}{\sqrt{\rho}}

而"什么都不做"的 HODL 策略始终持有最初比例的 x0,y0x_0, y_0

无常损失(以 Y 计价,做市价值 / HODL 价值 - 1):

IL=2ρ1+ρ1\text{IL} = \frac{2\sqrt{\rho}}{1+\rho} - 1

推导思路:

  1. 设初值 L=x0y0L = \sqrt{x_0 y_0}(几何平均),初始价格 P0=y0/x0P_0 = y_0/x_0
  2. 价格变化后,做市组合价值(以 Y 计价):
Vmm=xmmP1+ymm=LρP0ρ+LρV_{\text{mm}} = x_{\text{mm}} \cdot P_1 + y_{\text{mm}} = L\sqrt{\rho} \cdot P_0\rho + \frac{L}{\sqrt{\rho}}
  1. HODL 价值:
Vhodl=x0P1+y0=x0P0ρ+y0V_{\text{hodl}} = x_0 P_1 + y_0 = x_0 P_0\rho + y_0
  1. 代入 x0=L/P0,y0=LP0x_0 = L/\sqrt{P_0}, y_0 = L\sqrt{P_0} 并化简即得上式。IL 恒 0\le 0,只有 ρ=1\rho=1 时为 0。

19.5.2 IL 的数值特性

价格比率 ρ\rho价格变化IL
----------------------------
1.00%0.00%
1.25+25%0.60% 损失
1.5+50%2.02% 损失
2.0+100%5.72% 损失
3.0+200%13.40% 损失
4.0+300%20.00% 损失
0.5-50%5.72% 损失(对称)

对称性:IL 只取决于 ρ=P1/P0\rho = P_1/P_0,与涨跌方向无关——涨一倍和跌一半的 IL 相同(都是 ~5.7%)。


19.5.3 手续费收益的补偿模型

LP 的总收益 = 手续费累计 - IL。Uniswap V2 每笔 0.3% 手续费留存池中,k 增长即 LP 资产增值。

年化手续费收益估算:

APRfee=volume_fee_pct×VolumeTVL×Fee%0.3%\text{APR}_{\text{fee}} = \frac{\text{volume\_fee\_pct} \times \text{Volume}}{\text{TVL}} \times \frac{\text{Fee\%}}{0.3\%}
  • 若一个池子 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)

typescript

// 无常损失量化:从零实现(纯 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+ρ)12\sqrt{\rho}/(1+\rho)-1,只取决于价格比率、与涨跌方向对称;② 做市是否划算 = 手续费 APR 与 IL + 波动风险的赛跑;③ 稳定币对是"低波动低IL"的稳健生息选择,高波动币对是"高 APR 高 IL"的赌博。


, 前往 → ch19 总结 |*


第19章 总结:DEX 的数学、合约与经济

三个关键认知

  • 一条双曲线 xy=kxy=k 定义了整个 AMM。价格、滑点、无常损失全部由这一定恒乘积公式推导而来——理解公式就理解了 DEX。
  • 手续费留池让 LP 自动复利。0.3% 手续费不直接分配,而是留在池中使 kk 单调增长;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 持有

价格变化做市(AMM)HODLIL
------------------------------
ρ=1\rho=1不变不变0%
ρ=1.5\rho=1.5价值略降价值较高-2.0%
ρ=2\rho=2明显损失相对更好-5.7%
ρ=3\rho=3损失加重相对更好-13.4%
加手续费收益有 0.3%/笔复利补偿来源
攻击本质防御
------------------
闪电贷操纵单区块资金约束归零TWAP / 外部预言机
三明治MEV 前插后插MIN_OUT + deadline,教育用户
重入回调重复取款checks-effects-interactions + 锁

核心公式与代码索引

概念公式章节验证
------------------------
恒定乘积xy=kxy=k19.2
交换输出Δy=yΔx(1f)/(x+Δx(1f))\Delta y = y\Delta x(1-f)/(x+\Delta x(1-f))19.2/19.3✓ 2961 USDC
即时价格P=y/xP=y/x19.2
TWAPP=Δcum/Δt\overline{P} = \Delta\text{cum}/\Delta t19.4✓ =1
无常损失IL=2ρ/(1+ρ)1\text{IL}=2\sqrt{\rho}/(1+\rho)-119.5✓ -5.72%

下一章衔接

你已经完成了一个从数学到合约到安全的完整 DEX。以此为基石,后续可延伸:集中流动性做市(Uniswap V3)、多 hop 路由、订单簿 DEX(如 dYdX)、以及把 DEX 接入聚合器与生态。至此,区块链教程的 19 章主体全部落地。


, 完结 · 附录 A 工具链 |*

评论

0

评论加载中…

发表评论

0/2000