Uniswap V2 第 1 章:总览(Overview)
这一章带你建立对 Uniswap V2 的整体认识:它用什么数学做市(恒定乘积
x·y=k),由哪些合约组成(Core 与 Periphery),以及 Pair / Factory / Router 三者怎么分工。后面每一章都是在这个骨架上展开。
目录
- 1. Uniswap V2 是什么
- 2. 恒定乘积做市:x · y = k
- 3. 案例:直观感受 x·y=k 的报价
- 4. Core 与 Periphery:两层合约架构
- 5. 三个核心合约:Factory / Pair / Router
- 6. 一次 swap 的合约调用链
- 7. 本章小结
- 8. 动手练习
1. Uniswap V2 是什么
Uniswap V2 是以太坊上最经典的去中心化交易所(DEX)和自动做市商(AMM)。它没有订单簿,价格完全由池子里两种代币的数量比例决定。任何人都可以:
- 交易(swap):用一种代币换另一种。
- 提供流动性(add liquidity):存入两种代币,赚取交易手续费。
- 创建交易对(create pool):为任意两种 ERC20 代币开一个新池子。
它的全部定价逻辑建立在一条极简的方程上——恒定乘积。
2. 恒定乘积做市:x · y = k
设池子里有 x 个代币 A 和 y 个代币 B,Uniswap V2 维持:
x · y = k (常数)
- 交易时,乘积
k保持不变(不考虑手续费的话)。 - 你往池子里加入 A(x 增大),就必须取走一些 B(y 减小),才能让乘积不变。
- 取走的 B 的数量,就是你”换到的量”。
这条曲线是一条双曲线,关键性质:
- x、y 永远 > 0(双曲线不碰坐标轴)→ 池子永远不会被完全掏空。
- 交易越大,价格移动越多 → 滑点随交易规模增大。
3. 案例:直观感受 x·y=k 的报价
设池子 x = 100 WETH,y = 300,000 DAI,则 k = 100 × 300,000 = 30,000,000。
当前价格(现货价)= y/x = 300,000/100 = 3,000 DAI/WETH。
你想卖 1 个 WETH 换 DAI(先忽略手续费):
新 x = 100 + 1 = 101
新 y = k / 新x = 30,000,000 / 101 = 297,029.70 DAI
你换到的 DAI = 300,000 - 297,029.70 = 2,970.30 DAI
注意:现货价是 3,000,但你实际只换到 2,970.3 DAI,差的 29.7 就是滑点(因为这不是无穷小交易)。卖得越多,平均成交价越差。这就是恒定乘积的核心特征。
4. Core 与 Periphery:两层合约架构
Uniswap V2 的代码分两个仓库、两层职责:
| 层 | 仓库 | 职责 | 特点 |
|---|---|---|---|
| Core(核心) | v2-core | 存资产、维护 x·y=k、铸销 LP | 极简、最小化、永不升级(安全第一) |
| Periphery(外围) | v2-periphery | 帮用户算路由、加滑点保护、处理 ETH/WETH | 方便、可替换、面向用户 |
设计哲学:Core 尽量简单、把钱锁死的逻辑做到最小(减少出 bug 的可能);所有”方便但复杂”的逻辑放到 Periphery,即使 Periphery 有问题也不会动到 Core 里的资产安全。
你日常用钱包/前端交易时,调用的是 Periphery 的 Router,由它再去调 Core 的 Pair。直接调 Core 是高级用法(需要自己处理安全检查)。
5. 三个核心合约:Factory / Pair / Router
| 合约 | 所在层 | 作用 |
|---|---|---|
| UniswapV2Factory | Core | 工厂:为任意两种代币创建并登记 Pair(每对代币唯一一个池子) |
| UniswapV2Pair | Core | 池子本体:存两种代币、维护 x·y=k、swap/mint/burn、是一个 ERC20(LP token) |
| UniswapV2Router02 | Periphery | 路由:多跳交易、滑点保护、deadline、ETH↔WETH 转换,是用户主要入口 |
关键点:
- 每个 Pair 同时也是一个 ERC20——它发行的代币就是 LP token(流动性凭证)。
- Factory 保证 每对代币只有一个 Pair(避免流动性分散)。
- Router 自己不存钱,只是帮你把调用编排好、转发给 Pair。
6. 一次 swap 的合约调用链
以”用 WETH 换 MKR,路径 WETH → DAI → MKR”为例:
用户
│ 调用 router.swapExactTokensForTokens(amountIn, ..., path=[WETH,DAI,MKR])
▼
Router02
│ ① 用 Library.getAmountsOut 算出每一跳能换多少
│ ② 把 WETH 转入第一个 Pair(WETH/DAI)
│ ③ 依次调用各 Pair.swap(...)
▼
Pair(WETH/DAI) → Pair(DAI/MKR)
│ 每个 Pair 校验 x·y=k 仍成立(含手续费),把输出币转给下一跳/用户
▼
用户收到 MKR
记住这个分层:Router 负责”编排和计算”,Pair 负责”守住 k 和转账”。 后面各章会逐个拆开这些函数。
7. 本章小结
- Uniswap V2 是基于恒定乘积
x·y=k的 AMM:池子永不被掏空,但滑点随交易规模上升。 - 现货价 =
y/x;实际成交含滑点,交易越大成交价越差。 - 代码分 Core(v2-core,极简安全) 和 Periphery(v2-periphery,方便面向用户) 两层。
- 三大合约:Factory(建池)、Pair(池子本体,也是 LP token 的 ERC20)、Router(用户入口,多跳/滑点/ETH 处理)。
- swap 调用链:用户 → Router(算路由、转发)→ 一个或多个 Pair(守 k、转账)。
8. 动手练习
目标:先在主网分叉上读取一个真实 Pair 的储备量,亲手算一次现货价和一笔 swap 的滑点。
练习:读取 Pair 储备并手算报价
主网分叉,针对 DAI/WETH 的 Pair(可用 Factory 的 getPair(DAI, WETH) 查到,或用常量 UNISWAP_V2_PAIR_DAI_WETH):
interface IUniswapV2Pair {
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32);
function token0() external view returns (address);
function token1() external view returns (address);
}
思路:
- 调
getReserves()拿到reserve0、reserve1,用token0()确认哪个是 DAI、哪个是 WETH。 - 计算现货价
reserve_DAI / reserve_WETH,打印(应接近当前 ETH 市价)。 - 手算”卖 1 WETH 能换多少 DAI”(用第 3 节的
新y = k/新x方法,先不含费)。 - (进阶)再算上 0.3% 手续费:输入按
amountIn × 997/1000计入(见第 2 章公式),对比含费和不含费的差别。
运行
forge test --evm-version cancun --fork-url $FORK_URL \
--match-path test/UniswapV2Overview.t.sol -vvv
下一章(第 2 章 Swap)深入兑换:
getAmountsOut/getAmountsIn的精确公式(含 0.3% 手续费)、pair.swap怎么守住 k,以及滑点的数学。