Summary
目前Uniswap是Defi现货交易的一个重要基础,只要你想接触区块链就会碰到它,并且它的设计范式是被整个行业参考和借鉴。从2018年开始,截止到目前为止一共更新了四个版本,每个版本都有自己的特性,接下来我们逐一讲解
V1
V1版本是Hayden Adams工程师用Vyper(pyhton版的evm语言)实现的一版,主要证明了
- 恒定乘积AMM是能跑的
- 引入了 LP token概念
- 不需要对手方也可以成交的
核心公式就一个 x * y = k
这个版本也有很强烈的限制,比如
- 仅支持ETH<-> ERC20池,任意一个token必须和ETH做对
- 手续费固定0.3%
- 没有价格预言机
这些问题也催生出了V2
V2
V2版本于2020年,它保留了V1的 x * y = k公式,在生态层面作出了几个关键升级。
任意ERC20/ERC20池
放弃了V1必须走ETH的路线,允许任意两种ERC20直接组池。
这个变化的连锁反应超出预期:
- 稳定币直兑池爆发——
USDC/USDT、DAI/USDC池深度很快超过 CEX - 新代币发行更灵活——项目方发币时可以直接和自己的稳定币建池,不必先兑 ETH
- 长尾市场繁荣——SUSHI/YAM、YFI/YFV 这些 DeFi meme 时代的核心资产都在 V2 上诞生
TWAP预言机
在每一个pair合约(一个交易对,USDC/WETH)中记录一个累积价格,每次swap时进行更新,
priceCumulativeLast += 当前价格 * 时间间隔
例如:在T1时刻记录一次priceCumulativeLast,T2时刻再记一次,获取时,者着相减/时间差,得到T1到T2之间的时间加权平均价格。
举个例子:
| 时间 | 事件 | 价格 | 该价格持续时长 |
|---|---|---|---|
| 0s | 开始观察 | $2000 | 600s |
| 600s | 有人买入 ETH | 涨到 $2100 | 300s |
| 900s | 有人卖出 ETH | 跌到 $1900 | 900s |
| 1800s | 结束观察 | — | — |
累积价格的变化过程(假设 0 秒时读到的值是 0):
600s 时刻的 swap 触发更新:
累积价格 += 2000 × 600 = 1,200,000
900s 时刻的 swap 触发更新:
累积价格 += 2100 × 300 = 630,000 → 累计 1,830,000
1800s 时刻我读取(触发更新):
累积价格 += 1900 × 900 = 1,710,000 → 累计 3,540,000
套入公式:
TWAP = (结束时累积价格 - 开始时累积价格) / 时间差 = (3,540,000 - 0) / 1800 = $1966.67
这种机制可以有效的防止被闪电贷在单块内操纵价格
手续费开关(Fee switch)
V1中所有的手续费都给了LP,在V2中引入了一个开关,这个开关通过治理打开,一打开,0.3%手续费中的1/6就会被切到协议金库,这个开关,好像从上线开始到现在一直没有打开过,直到2024年,才在部分池子中开启。
合约Core/Periphery分离
V2 将整体合约分为Core/Periphery两部分,Core包含
- UniswapV2Factory(所有的pair都有此创建)
- UniswapV2Pair (所有的钱都在这里)
- UniswapV2ERC20(Pair继承的LP Token的实现,支持permit离线签名操作)
作为核心,极简、不可升级、并且管钱。
Periphery(外围合约)包括
- UniswapV2Router02 (用户的实际入口,多跳路径规划、滑点保护等功能都在这里)
- UniswapV2Library (工具库)
这种设计让核心合约(pair)保持极简,业务逻辑放在 Router。Router 可以升级、可以被别的协议替换。这个"Core / Periphery"分离思想被后续所有 Uniswap 版本沿用。
V3
V3是在2021年上线的,这次升级让pool的资本效率提升了100倍以上,同时对散户的门槛也提高了,是个对散户不是很友好的版本。
CLMM(集中流动性)
在一个 USDC/USDT 池子中。这两个稳定币的价格几乎永远在 $0.999 ~ $1.001 之间波动。但 V2 的公式是 x * y = k——流动性均匀分布在 0 到 ∞ 的所有价格区间,例如下图:
流动性密度
│
│ ━━━━━━━━━━━━━━━━━━━━━━━ ← V2:每个价格上摆的流动性都一样(均匀分布)
│
└──────────────────────── 价格
0 1 ∞
在以前情况,池子都要保持流动性
- 价格 = 0.5 时:需要有钱(万一价格砸到那)
- 价格 = 2 时:需要有钱(万一价格涨到那)
- 价格 = 10、100、10000 时:也都得有钱
结果就是:99.9% 的流动性趴在永远不会被用到的价格区间。你放 100 万美元进 USDC/USDT V2 池,真正在 $0.999-$1.001 之间被使用的可能只有 1000 美元。资本效率 = 0.1%,证明过程如下,包含挺多的数学公式,不想看的可以跳过:
证明:
第一步:把池子状态用公式表示
V2 公式:x * y = k(x = USDC 数量,y = USDT 数量)
价格定义:p = y/x(1 个 USDC 值多少 USDT)
由这两条可以解出,任意价格 p 时池子里的资产量:
x = √(k/p) y = √(k·p)
第二步:代入"100 万美元、价格 = 1"
价格 p = 1 时,x = y = √k,池子总价值:
总价值 = x + y = 2√k = $1,000,000
所以 √k = 500,000
即池子里:50 万 USDC + 50 万 USDT
第三步:算"价格从 1.001 走到 0.999,池子里实际动了多少钱"
"被用到的资金" = 价格在这条缝里来回时,真正参与成交(被换进换出)的资产量。
价格 p = 1.001 时:
y = √k × √1.001 = 500,000 × 1.00049988 ≈ 500,250 USDT
价格 p = 0.999 时:
y = √k × √0.999 = 500,000 × 0.99949987 ≈ 499,750 USDT
USDT 的变动量:
Δy = 500,250 − 499,750 ≈ 500 USDT
同样算 USDC(x = √(k/p)):
p=0.999 时 x ≈ 500,250, p=1.001 时 x ≈ 499,750
Δx ≈ 500 USDC
第四步:得出结论
价格在 $0.999 ↔ $1.001 之间扫完整个区间,池子里真正被换手的资产:
Δx + Δy ≈ 500 + 500 = $1,000
资本效率:
1,000 / 1,000,000 = 0.1%
其余 99.9 万美元,无论价格在这条缝里怎么震,一动不动 —— 它们只有在价格跑出这个区间时才会被动用。
V2的资金利用率就好像你开了一家只做午市的餐厅,但你雇了 100 个服务员 24 小时三班倒站岗 —— 半夜 3 点也有很多服务员站在空餐厅里,做服务的永远只有午市的那几个服务员,剩下的服务员工资都白给了。
V3出来之后,会让提供流动性之前让你自己选择一个价格区间,同样 100 万美元产生的有效流动性相当于 V2 的 ~500 倍(因为流动性密度提升了)。但是同时也增加了LP的工作,你需要主动管理LP,当LP中只有单边时,会停止赚手续费,需要你手动调整,每次调整还需要付gas,还可能会重新开仓,所以散户可以直接放弃这个版本了。
Fee Tier
以前V2中,手续费固定0.3%,V3则提供4档
| Fee Tier | 目标资产类型 |
|---|---|
| 0.01% | 稳定币互换(USDC/USDT、DAI/USDC) |
| 0.05% | 稳定币和挂钩资产(stETH/ETH)、蓝筹(USDC/ETH) |
| 0.30% | 中等波动资产(ETH/大部分 ERC20) |
| 1.00% | 高波动 / 长尾资产 |
Tick系统
Tick
这个是为了解决LP任意边界而产生的,先看没有Tick的世界有多糟
假设允许任意边界,池子里有三个 LP:
张三: [0.99871, 1.00133] 放了 10 万
李四: [0.99902, 1.00087] 放了 10 万
王五: [0.99913, 1.00121] 放了 10 万
现在价格从 1.0008 涨到 1.0009。合约必须回答一个问题:“这一步有没有跨过谁家的边界?”
怎么回答?只能把所有仓位挨个查一遍:张三的上边界 1.00133 过了吗?李四的 1.00087 过了吗?王五的呢……池子里有 1 万个 LP,就查 1 万次。每笔 swap 都这么查,gas 天文数字。
问题的根源:每个人的边界都是"私有的",合约必须认识每一个仓位(这个问题是不是很像开发过程中,你需要遍历所有的数组的情况),在EVM中每次读取一次数据,会有2100的gas,如果每一个仓位都要读取的话,那gas消耗就不是一般的大了。
解决:价格的边界只能选在预设刻度上(tick)。于是:
张三: [tick -13, tick +13] (计算公式: tick = ln(price) / ln(1.0001), 为什么用1.0001,是因为 log_1.0001(x) 让每个 tick 之间的价格差 ≈ 0.01%(1 basis point)——这是 CEX 交易员熟悉的最小 tick size)
李四: [tick -10, tick +9]
王五: [tick -9, tick +12]
关键变化发生了 —— 边界的"种类"变得有限了。张三、李四、王五各自的钱不同、区间不同,但他们的边界都落在 -13, -10, -9, +9, +12, +13 这六根钉子上。
于是合约可以换一种记账方式:不给"人"记账,给"钉子"记账。
每根被使用的钉子上挂一个数字,叫 liquidityNet(流动性净变化):
钉子 -13: +10万 ← 价格从下往上跨过这里时,张三的钱上岗 钉子 -10: +10万 ← 李四的钱上岗 钉子 -9: +10万 ← 王五的钱上岗 钉子 +9: -10万 ← 李四的钱下岗 钉子 +12: -10万 ← 王五的钱下岗 钉子 +13: -10万 ← 张三的钱下岗
现在价格上涨跨过钉子 +9 时,合约根本不需要知道李四是谁,它只做一个动作:
当前在岗流动性 = 30万 + (-10万) = 20万
一次加法,完事。1 万个 LP 和 3 个 LP,跨钉子的成本一模一样 —— 因为所有人的边界都被"合并同类项"到了有限的钉子上。
这就是不允许边界,预设刻度的全部含义,让合约按tick记账,而不是按照人来记账(你的明细存放在你自己的仓位NFT上)。
新问题:
按照tick来记账,怎么才能快速的找到下一个tick呢,V3的tick范围是 -887272 到 +887272,约 177 万根钉子。但刚才的池子里只有 6 根钉子有流动性,其余177多万根全是空的,那要怎么才能快速找到呢。
V3使用Bitmap:用 1 个比特标记 1 根钉子
解法是给全部钉子建一张位图(bitmap) —— 每根钉子对应 1 个比特:
该钉子挂了账(被初始化) → 1 该钉子是空的 → 0
以太坊的一个存储槽是 256 位,正好可以打包 256 根钉子的状态:
槽 0 管 tick 0~255: 0000000001000000…0010000000… ↑ ↑ tick 9 = 1 tick 12 = 1
现在"找下一根有账的钉子"变成了:
① 读 1 个存储槽(拿到 256 根钉子的 0/1 状态) ← 1 次读,2100 gas ② 用位运算在这 256 位里找下一个 1 ← 纯计算,几乎免费
一次读取覆盖 256 根钉子。就算下一根有账的钉子在几千格外,也只需要读十几个槽,而不是几千次。这就是 bitmap 的全部作用:把"大海捞针式的逐格扫描"压缩成"一页页翻目录"。
Tick spacing(间距)
tick一共有177多万根钉子,如果每一根都使用,那算下来也是一笔很大的gas消耗,所以针对不同的代币,使用不用的spacing,例如:
- 主流币池(0.30% 费率, spacing = 60),每 60 根钉子才有一根能用
- 长尾币池(1.00% 费率, spacing = 200),每 200 根钉子才有一根能用
V4
V4于2025年1月上线,代号Hooks + Singleton。这一版主要是让Uniswap从"一个协议"变成了"一个可编程 DEX 引擎"。
Hooks - 核心
Hooks 让每个池子可以挂一个外部智能合约作为"回调",在特定 swap 生命周期节点被自动调用:
| Hook 时点 | 触发时机 |
|---|---|
beforeInitialize | 池子创建前 |
afterInitialize | 池子创建后 |
beforeAddLiquidity | LP 添加流动性前 |
afterAddLiquidity | LP 添加流动性后 |
beforeRemoveLiquidity | LP 移除流动性前 |
afterRemoveLiquidity | LP 移除流动性后 |
beforeSwap | swap 前 |
afterSwap | swap 后 |
beforeDonate | 捐赠前 |
afterDonate | 捐赠后 |
有了 Hook,你可以在同一个 Uniswap V4 引擎上实现:
- 动态手续费:
beforeSwap里根据市场波动率调整 fee(这个也是路由服务里面一个重要的工程) - 限价单:
afterSwap里检查当前价格,达到用户预设条件时自动执行 - KYC 池:
beforeSwap里验证调用者是否在白名单 - 自定义预言机:
afterSwap里更新自己的 TWAP 存储 - MEV 保护:
beforeSwap里检测 sandwich 并拒绝 - 激励分发:
afterAddLiquidity里给 LP 空投别的代币
这就是 “DEX 变平台"的意思——之前想做这些事得部署一个新的 DEX(Bancor 特殊设计、Balancer 加权池、Curve StableSwap)。现在都可以是V4 上的一个 Hook 合约。
Singleton架构
V2/V3 里,每个 pair 都是独立部署的合约。以太坊主网上有数万个 V2/V3 pair 合约。这意味着:
- 每次跨多个池子路由,都要跨合约调用(贵)
- 部署新池子要花 gas
- 数据是碎片化的(要查 100 个池子的状态得读 100 个合约)
V4 把所有池子塞进一个合约(PoolManager),池子只是这个合约里的一个数据结构(storage)。
好处:
- 创建新池子 gas 降低 99%(从部署合约变成写几个 storage slot)
- 多跳 swap 只需要一次合约调用
- 支持"闪电记账”(flash accounting)——一次交易内的多个 swap 可以只做净结算,中间状态不落地
Uniswap X
在跨链场景中,如果你想swap 10000USDC -> ETH,传统流程是,调用Router计算最优路径,签一笔交易,支付gas,承担滑点,并且自己防范MEV(三明治)攻击。
而Uniswap X的思路是,用户不再签"我要在池子 X 用路径 Y swap",而是签"我要用最多 10000 USDC 换到不少于 5.5 ETH"(intent)。
然后Filler 网络,会去竞争这个intnet,流程如下:
- 你签一个 EIP-712 结构化订单,广播到 Uniswap X orderflow 网络
- 各个 filler 在链下算怎么最便宜地填充你(他们可能用自己的库存、可能路由到 Uniswap V4、可能路由到 CEX 库存桥回来)
- 出价最高的 filler 中标,链上执行,并且无MEV。
- 你付 0 gas(filler 付),拿到承诺的最低金额
底层机制: filler(做单人)在目标链先垫付你要的币,再回源链领走你付的币 —— 由 The Compact(资源锁)做安全结算
目前这个版本已经上线,最新的动态是已经在Robinhood Chain上线。
PS:这个感觉就很像广告中的RTB。
最后说一句
感觉区块链就是数字的游戏,里面的包含太多的数学知识了
