6月13日, Uniswap Labs 发布了博客,宣布推出了 Uniswap V4 的草案代码,包括 Uniswap v4 核心和周边库的开源早期版本以及技术白皮书的草案。Uniswap V4 出现了许多新的特性,本文将结合其草案代码以及白皮书进行相应的分析。
V4 新特性
在 Uniswap V4 中引入了 hooks,可以由池创建者进行定制化的功能;同时放弃了原来的通过工厂合约建立流动性池的方式,所有池都在一个合约中;使用 Flash accounting 方式节省 Gas;支持原生的 ETH;同时还有一些其他的新特性。
Singleton 取代 Factory
Uniswap 的 V2 和 V3 版本都是通过工厂合约创建新的流动性池,每一个流动性池都是一个单独的合约,因此创建新的流动性池也就意味着会创建一个新的合约,这样会耗费大量的 Gas;而在 V4 版本中则是使用了 Singleton 模式,所有的流动性池都在一个合约中,创建新的流动性池不再需要创建合约,流动性池数据都保存在合约的映射中,通过poolId 指向 pool.State,poolId 则由 PoolKey 进行哈希并转换成 uint 得到,而 pool.State 则存储 pool 的数据,这样做大大降低了创建新流动性池的 Gas。

在 V4 中通过 PoolManager 合约中的 initialize 函数创建新的流动性池,需要传入 PoolKey 结构体以及价格,其中的 PoolKey 结构体如下:



接下来,如果 hook 设置了在初始化前调用的话则会去调用 hook 合约的 beforeInitialize 函数,执行 hook 中设定的逻辑。
然后将传入的 PoolKey 转换为 bytes32 类型的 poolId,并通过 protocolFeeController 合约以及 hook 合约获取各种费用的值。
然后调用 initialize 对 pool 的 State 进行初始化:


可以看到在 V4 中新建流动性池不会再使用 Create2 去新创建合约,取而代之的是将 pool 的信息保存在 pools 这个 mapping 中,这样做大大的节省了新建流动性池操作的 Gas,所有的流动性池的信息都在 PoolManager 合约中。
使用 Hooks 提供定制化功能
在 Uniswap V4 中引入了 hook 的概念,可以在流动性池的调用的生命周期内某些指定点执行一些自定义的逻辑,hook 功能增加了流动性池的灵活性,可以执行更多的富有创造力的功能。
Uniswap V4 目前支持在 8 个特定的位置进行 hook 回调:
- beforeInitialize / afterInitialize
- beforeModifyPosition / afterModifyPosition
- beforeSwap / afterSwap
- beforeDonate / afterDonate


- 几何平均预言机(GeomeanOracle.sol)
- 限价单(LimitOrder.sol)
- 时间加权平均做市商(TWAMM.sol)
- 波动率预言机(VolatilityOracle.sol)

在 Uniswap V4 中一个新的设计是 Flash accounting。在 Uniswap 的早期版本中,像 swap 或者 addLiquidity 等操作都是以代币的转移结束,而在 V4 中,每一个操作会更新内部的一个净余额(delta),在所有操作结束时会校验该值是否为 0,必须保证该值为 0 才能交易成功。当 Flash accounting 和 Singleton 结合时,可以大大简化多跳交易。在 PoolManager 合约中新增了 take 和 settle 函数,分别用于向池中借出、存入资金,通过调用这两个函数,保证调用结束时不欠 PoolManager 合约或调用者任何代币。
下面以官方的 foundry 测试合约中测试 donate 函数为例进行分析,大概的流程如下:
- donateRouter 合约调用 PoolManager 合约的 lock 函数;
- PoolManager 合约的 lock 函数会回调 donateRouter 合约(调用者)的 lockAcquired 函数;
- donateRouter 合约的 lockAcquired 函数会调用 PoolManager 合约的 donate 函数,并转移代币,然后调用 settle 函数;
- 回到 lock 函数,校验 delta 是否都为 0 。






执行完 PoolManager 合约中 donate 函数的逻辑后执行流回到 lockAcquired 函数中,并执行接下来的转账、调用 PoolManager 的 settle 函数等操作:

settle 函数中先通过 reservesOf 获取内部记录的代币余额,然后在通过 currency.balanceOfSelf() 获取真正的代币余额,相减得到用户转移过来的代币的数量,然后再通过 _accountDelta 修改对应的净余额,这里传入的值做了取负处理,因此数额正确的话,净余额会被修改为 0:



在 Uniswap V2 、V3 中,并不支持使用原生的 ETH作为代币建立流动性池,而是将 ETH 包装成 WETH 来使用,而在 Uniswap V4 中由于 Singleton 和 Flash accounting 的设计,其支持使用原生的 ETH 建立流动性池。这样做更加节省 Gas,原生 ETH 转账需要的 Gas 仅为 ERC20 代币转账的一半,同时使用时也不需要将 ETH 转换为WETH。
在 Uniswap V4 的 v4-core-main/contract/libraries/CurrencyLibrary.sol 文件中,定义当 代币的地址为 0 地址则是原生的 ETH,同时提供了 transfer 方法,当代币是 ETH 时使用 call 进行发送:

Uniswap Labs 推出的 Uniswap V4 版本草案发布了新多的创新及优化,如 Hook 的引入,Singleton 模式设计以及 Flash accounting 的设计等等,相信未来会有越来越多的有趣的实验在 V4 上发生,通过 Hook 开发者会开发出创新的代码来扩充流动性池的功能,而用户也能通过 Singleton 的设计使得自己创建流动性池的费用大大降低,同时 Flash accounting 以及 NATIVE ETH 的支持也会使得用户的交易费用变的更低,更加方便。
关于我们
At Eocene Research, we provide the insights of intentions and security behind everything you know or don’t know of blockchain, and empower every individual and organization to answer complex questions we hadn’t even dreamed of back then.
了解更多: Website | Medium | Twitter
参考
- Uniswap/v4-core
- Uniswap/v4-periphery
- Our Vision for Uniswap v4
- whitepaper-v4-draft.pdf