死磕Uniswap V2(一):V2概述与核心原理

本文是「死磕Uniswap V2」系列的第一篇,深入剖析V2的核心原理——恒定乘积自动做市商(AMM)机制。

系列导航

序号标题核心内容
01V2概述与核心原理恒定乘积AMM、核心公式
02Factory与Pair合约合约结构、创建流程
03流动性与LP代币mint/burn、份额计算
04交换机制深度解析swap函数、滑点、Flash Swap
05价格预言机TWAP、价格计算
06Router与路由最佳路径、多跳交易
07安全实践与最佳实践漏洞防护、开发建议

1. AMM技术演进

1.1 什么是AMM?

传统交易所采用订单簿(Order Book)模式,需要买方和卖方的价格匹配。而AMM(Automated Market Maker)通过数学公式自动定价,无需对手盘即可交易。

graph TB
    subgraph OrderBook["订单簿模式"]
        OB1[买单列表]
        OB2[卖单列表]
        OB3[价格匹配]
        OB1 --> OB3
        OB2 --> OB3
    end

    subgraph AMM["AMM模式"]
        AMM1[流动性池]
        AMM2[自动定价公式]
        AMM3[即时交易]
        AMM1 --> AMM2
        AMM2 --> AMM3
    end

    style AMM2 fill:#c8e6c9

1.2 Uniswap发展历程

flowchart LR
    V1["Uniswap V1<br/>2020.5<br/>ETH/ERC20"] --> V2["Uniswap V2<br/>2020.8<br/>ERC20/ERC20"]
    V2 --> V3["Uniswap V3<br/>2021.5<br/>集中流动性"]
    V3 --> V4["Uniswap V4<br/>2023.6<br/>Hooks可编程"]

    style V2 fill:#e3f2fd

2. 恒定乘积公式

2.1 核心公式

Uniswap V2基于恒定乘积公式:

x × y = k

变量含义:

  • x: Token0的储备量
  • y: Token1的储备量
  • k: 恒定乘积常数

核心思想: 交易前后,两种代币储备量的乘积保持不变。

2.2 公式可视化

graph LR
    subgraph Before["交易前"]
        X1["x = 1000"]
        Y1["y = 1000"]
        K1["k = 1,000,000"]
    end

    subgraph Swap["执行交易"]
        Direction["用户用100 Token0<br/>换取~91 Token1"]
    end

    subgraph After["交易后"]
        X2["x = 1100"]
        Y2["y = 909"]
        K2["k = 999,900 ≈ 1,000,000"]
    end

    Before --> Swap
    Swap --> After

    style K1 fill:#c8e6c9
    style K2 fill:#c8e6c9

2.3 价格推导

根据恒定乘积公式,代币的相对价格为:

P = y / x

即:1个Token0的价值 = y/x个Token1

价格弹性:

  • 当x增加时,y减少
  • 价格根据供需自动调整
  • 提供的流动性越深,滑点越小

3. 交换公式详解

3.1 输入输出计算

假设用户用Δx数量的Token0换取Token1:

输入Token0,输出Token1:

Δy = y × Δx / (x + Δx)

输入Token1,输出Token0:

Δx = x × Δy / (y + Δy)

3.2 交换示例

场景: ETH/USDC池子

  • 初始储备:x = 100 ETH,y = 200,000 USDC
  • 用户用10 ETH换取USDC
flowchart LR
    A["初始状态<br/>x=100 ETH<br/>y=200,000 USDC"] --> B["输入: Δx=10 ETH"]
    B --> C["计算输出<br/>Δy = 200,000 × 10 / (100 + 10)"]
    C --> D["输出: Δy ≈ 18,181 USDC"]
    D --> E["最终状态<br/>x=110 ETH<br/>y=181,819 USDC"]

代码实现:

function getAmountOut(
    uint256 amountIn,
    uint256 reserveIn,
    uint256 reserveOut
) external pure returns (uint256 amountOut) {
    require(amountIn > 0, "Insufficient amount");
    require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
 
    uint256 amountInWithFee = amountIn * 997; // 扣除0.3%费用
    uint256 numerator = amountInWithFee * reserveOut;
    uint256 denominator = reserveIn * 1000 + amountInWithFee;
 
    amountOut = numerator / denominator;
 
    return amountOut;
}

4. 滑点现象

4.1 什么是滑点?

滑点(Slippage):实际成交价格与预期价格的差异。

graph TB
    subgraph NoSlippage["无滑点(理想)"]
        P1["预期价格: 1 ETH = 2000 USDC"]
        P2["成交价格: 1 ETH = 2000 USDC"]
        P3["滑点: 0%"]
    end

    subgraph WithSlippage["有滑点(实际)"]
        P4["预期价格: 1 ETH = 2000 USDC"]
        P5["实际价格: 1 ETH = 1818 USDC"]
        P6["滑点: 9.1%"]
    end

    style P6 fill:#ffcdd2

4.2 滑点计算

滑点公式:

滑点 = |预期价格 - 实际价格| / 预期价格 × 100%

滑点的影响因素:

  1. 交易规模 - 交易量越大,滑点越大
  2. 池子深度 - 储备越多,滑点越小
  3. 储备比例 - 储备比例越均衡,滑点越小

4.3 滑点保护代码

function swap(
    uint256 amount0Out,
    uint256 amount1Out,
    address to,
    bytes calldata data
) external {
    // ... 省略部分代码
 
    // 滑点保护
    require(amount0Out > 0 || amount1Out > 0, "Insufficient output amount");
 
    if (amount0Out == 0) {
        uint256 amount1In = msg.value;
        require(
            getAmountOut(amount1In, reserve1, reserve0) >= amount1Out,
            "UniswapV2: INSUFFICIENT_LIQUIDITY"
        );
    }
}

5. 流动性提供机制

5.1 LP(流动性提供者)角色

LP向池子提供两种代币,获得LP代币作为凭证:

flowchart LR
    A["LP提供者"] --> B["存入Token0"]
    A --> C["存入Token1"]
    B --> D["获得LP代币"]
    C --> D
    D --> E["按份额分享手续费"]

5.2 LP代币计算

LP代币数量:

amountLP = min(amount0 / reserve0, amount1 / reserve1) × totalSupply

计算逻辑:

  1. 分别计算两种代币的占比
  2. 取较小的占比(按比例最小的为准)
  3. 乘以当前LP总供应量

5.3 代码示例

function mint(address to) external lock returns (uint256 liquidity) {
    (uint112 reserve0, uint112 reserve1, ) = getReserves();
    uint256 balance0 = IERC20(token0).balanceOf(address(this));
    uint256 balance1 = IERC20(token1).balanceOf(address(this));
 
    uint256 amount0 = balance0 - reserve0;
    uint256 amount1 = balance1 - reserve1;
 
    uint256 _totalSupply = totalSupply;
 
    if (_totalSupply == 0) {
        // 首次提供流动性
        liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
    } else {
        // 追加流动性
        liquidity = min(
            amount0 * _totalSupply / reserve0,
            amount1 * _totalSupply / reserve1
        );
    }
 
    require(liquidity > 0, "Insufficient liquidity minted");
 
    _mint(to, liquidity);
 
    emit Mint(msg.sender, to, amount0, amount1, liquidity);
}

6. V2核心特性

6.1 ERC20/ERC20交易

V2最大的创新是支持任意两个ERC20代币的直接交易:

graph TB
    subgraph V1Mode["V1模式"]
        V1A["ETH必须参与"]
        V1B["ETH/TokenA"]
        V1C["ETH/TokenB"]
    end

    subgraph V2Mode["V2模式"]
        V2A["任意ERC20配对"]
        V2B["TokenA/TokenB"]
        V2C["TokenC/TokenD"]
    end

    V1B -.->|需要两笔交易| V2B
    V1C -.->|需要两笔交易| V2B

    style V2B fill:#c8e6c9

6.2 Flash Swap

Flash Swap允许用户无需抵押借出代币进行套利:

sequenceDiagram
    participant U as User
    participant P as Pair合约
    participant T as 目标合约

    U->>P: flashSwap(借100 Token0)
    P->>U: 转账100 Token0
    U->>T: 执行套利逻辑
    T->>P: 归还101 Token0
    P->>P: 验证:归还 >= 借出 + 费用

Flash Swap条件:

  1. 在同一个交易内完成
  2. 归还数量 >= 借出数量 + 0.3%费用
  3. 失败则整个交易回滚

6.3 TWAP预言机

V2内置**时间加权平均价格(TWAP)**预言机:

// 累积价格
uint256 public price0CumulativeLast;
uint256 public price1CumulativeLast;
 
// 更新预言机
function _update(
    uint256 balance0,
    uint256 balance1
) private {
    uint32 timeElapsed = block.timestamp - blockTimestampLast;
 
    if (timeElapsed > 0) {
        uint256 price0 = (reserve1 * 1e18) / reserve0;
        uint256 price1 = (reserve0 * 1e18) / reserve1;
 
        price0CumulativeLast += price0 * timeElapsed;
        price1CumulativeLast += price1 * timeElapsed;
    }
}

7. V2 vs V1/V3对比

7.1 功能对比

特性V1V2V3
交易对ETH/ERC20ERC20/ERC20ERC20/ERC20
流动性均匀分布均匀分布可集中
费用0.3%0.3%0.01%-1%
LP代币ERC20ERC20ERC721
Flash Swap
预言机TWAPTWAP
创建池简单简单复杂

7.2 适用场景

Uniswap V2 适合:

  • 标准 ERC20 代币对
  • 无需复杂价格管理的场景
  • 需要简单可靠的价格预言机
  • Flash Swap 套利策略

Uniswap V3 更适合:

  • 需要资本效率的场景
  • 稳定币对(窄区间)
  • 需要多级费率
  • 专业做市商

8. 本章小结

8.1 核心概念总结

mindmap
  root((V2核心))
    恒定乘积
      x × y = k
      自动定价
      无需订单簿
    ERC20/ERC20
      任意代币配对
      无需ETH中转
      灵活交易
    Flash Swap
      无抵押借贷
      原子化套利
      单交易完成
    TWAP预言机
      时间加权平均
      可靠价格源
      链上可验证

8.2 关键公式回顾

公式含义
x × y = k恒定乘积
P = y/x相对价格
Δy = y × Δx / (x + Δx)输出计算
滑点 = |预期-实际|/预期滑点计算

下一篇预告

在下一篇文章中,我们将深入探讨Factory与Pair合约,包括:

  • Factory合约的职责与实现
  • Pair合约的创建流程
  • 储备量管理机制
  • 事件日志设计

参考资料