Uniswap v4 Hook解析:架构设计、常见漏洞与防护实践

marsbit發佈於 2026-06-22更新於 2026-06-22

文章摘要

Uniswap v4 引入的 Hook 机制允许开发者在流动性池的关键生命周期事件中嵌入自定义逻辑,显著提升了灵活性,但同时也带来了新的安全挑战。本文解析了其核心架构:通过单例 PoolManager 合约集中管理所有池子状态,采用瞬时记账(flash accounting)确保交易结束时账目平衡,并通过 Hook 地址的低位比特编码其回调权限。 文章重点指出常见漏洞与防护实践: 1. Hook 的回调函数(如 beforeSwap)缺乏默认的访问控制,开发者需显式添加 onlyPoolManager 等修饰符以防未授权调用。 2. Hook 与池子的绑定不受协议限制,需在 beforeInitialize 中实现白名单或单池绑定逻辑。 3. Async Hook(自定义曲线)完全替代了 AMM 的 swap 逻辑,其安全性完全依赖 Hook 自身实现,需按独立金融合约标准进行审计。 4. Delta 记账机制仅保证账目最终平衡(NonzeroDeltaCount 归零),但不验证业务逻辑的正确性,Hook 可能通过操纵 delta 进行攻击。 5. 权限位必须与函数实现严格匹配,且地址通过 CREATE2 预先计算,升级时无法新增回调类型。 最后强调,每个 Hook 构成独立的信任域,其安全审计需覆盖完整的交互链路,项目方和审计方均需升级方法论以应对更复杂的风险。

自 Uniswap v4 主网上线后,Hook 机制成为 DeFi 最受关注的创新之一。Base 链上的 memecoin 发射平台 Flaunch 利用 Hook 实现固定预售价格与自动清算上线机制;流动性协议 Bunni v2 用 Hook 构建可编程流动性与再抵押模型;今年SATO、uPEG(Unipeg)、Slonks 等围绕 Hook 玩法的代币也在短期内创下数十倍涨幅。

在 Hook 生态繁荣的另一面,针对Hook实现缺陷的攻击也在显著上升。本文将从Uniswap v4 的 Hook 机制入手,逐步分析其核心调用栈,帮助项目方理解其中可能存在的漏洞。

Uniswap v4 Hook 安全

1. 简介

Uniswap v4 相对 v3 最显著的架构变化就是引入了 Hook(钩子)机制:允许开发者将自定义合约挂载到流动性池的生命周期事件上,在 swap、加减流动性、初始化等节点注入任意逻辑。

v4 的几个关键变更如下:

- Singleton 模式:所有池子的状态由单一的PoolManager 合约集中管理,不再为每个池子部署独立合约

- Flash accounting:交易过程中的中间余额变化只在 transient storage 中记账,仅在交易结束时统一结算

- Hook 机制:每个池子可以绑定一个 Hook 合约,PoolManager 会在关键节点(beforeInitialize、beforeSwap、afterAddLiquidity 等)回调该合约

- Hook 不可更换:一旦池子初始化完成,绑定的 Hook 地址永久固定(池子绑定的 Hook 地址不可修改,但 Hook 合约自身是否可升级取决于其实现方式)

在 v3 时期,开发者只需要信任 Uniswap 协议本身;而在 v4 时期,每个池子的安全性取决于它所绑定的 Hook。Hook 把 AMM 从一个固定的金融原语,拓展成了一个可编程的金融基础设施,但安全模型也从”协议级”碎片化到了”池子级”。

2. Hook 架构

2.1 PoolManager 与 unlock/callback 模型

v4 的核心合约是单例的 PoolManager。任何对池子的状态变更操作(swap、加减流动性)都必须先调用 PoolManager.unlock(),获得一次性的回调权限后,在unlockCallback() 中完成具体动作。整个流程结束时,PoolManager 会验证账本是否平衡:

NonzeroDeltaCount != 0 时直接 revert,这是 v4 flash accounting 的核心约束。任何 Hook 在执行过程中可以临时让账目失衡,但在交易结束前必须自行 settle,否则整笔交易回滚。

每个池子由PoolKey 结构唯一标识,其中包含 hooks 字段:

PoolId 由keccak256(PoolKey) 计算得出,因此 hooks 地址不同会产生不同的池子。这同时意味着,PoolManager 不会校验某个 Hook 地址是否曾被用于其他池子,同一个 Hook 合约可以被多个池子同时绑定。

2.2 Hook 权限位编码在地址中

v4 的一个反直觉设计是:Hook 的权限不是由合约内部的某个变量决定的,而是由 Hook 合约的部署地址决定的。

PoolManager 通过检查 Hook 地址的低 14 个比特位来判断该 Hook 是否需要在某个生命周期点被调用:

例如BEFORE_SWAP_FLAG = 1 << 7。若 Hook 地址第 7 位为 1,PoolManager 在 swap 前会调用该 Hook 的 beforeSwap();否则即使 Hook 合约实现了 beforeSwap(),也永远不会被 PoolManager 调用。

这意味着 Hook 部署时必须通过 CREATE2 + salt 计算出地址,构造出一个低位与目标权限完全一致的地址。Uniswap 官方提供了 HookMiner 工具用于此目的:

权限位与函数实现不一致时会产生两类问题:

(1) 实现了某个 hook 函数,但地址未编码对应权限位——PoolManager 永远不会调用该函数,逻辑形同虚设

(2) 地址编码了某个权限位,但 hook 没有实现对应函数——PoolManager 在回调时可能发生 revert实现DOS 或返回值校验失败,导致相关操作无法执行。

这同时是 Hook 升级的天然障碍:若 Hook 通过代理可升级,部署地址在升级时不变,因此升级后只能修改既有 hook 函数的实现,而不能新增 hook 类型。要预留未来扩展能力,必须在初始部署时把所有可能用到的权限位预先挖出来。

2.3 BaseHook 与一个被普遍忽视的访问控制陷阱

Uniswap v4 periphery 旧版本提供的 BaseHook 抽象合约,开发者可以继承它来实现自定义 Hook。BaseHook 的一个重要作用是为 unlockCallback() 函数提供onlyPoolManager 修饰符:

但是——这里存在一个非常容易被忽视的设计陷阱——早期版本的BaseHook 仅为unlockCallback 添加了onlyPoolManager,对其他 hook 回调函数(beforeSwap、afterSwap、beforeAddLiquidity 等)没有任何保护。这些函数的访问控制必须由 Hook 开发者自己显式添加。

3. Hook 生命周期代码走读

以一次 exact-input swap 为例,下面分析从用户发起交易到结算的完整调用栈。

3.1 池子初始化与 Hook 绑定

任何人都可以调用PoolManager.initialize() 创建新池子:

isValidHookAddress 只校验地址权限位与 fee 字段的兼容性,不校验 Hook 是否已经在其他池子中被使用,也不校验该 Hook 是否”愿意”接受这个 PoolKey。如果 Hook 设计时没有在 beforeInitialize 里加白名单或单池绑定逻辑,任何人都可以构造一个使用相同 Hook、但 token pair 任意的新池子并触发 Hook 后续的所有回调。

3.2 beforeSwap 与 BeforeSwapDelta

swap 流程入口是 PoolManager.swap(),它在执行核心 swap 逻辑前会调用 Hooks.beforeSwap():

beforeSwap 的返回值是一个三元组(bytes4, BeforeSwapDelta, uint24):

- bytes4:必须等于IHooks.beforeSwap.selector,否则 PoolManager 直接 revert

- BeforeSwapDelta:Hook 在本次 swap 中对 specified token 和 unspecified token 的 delta 调整

- uint24:动态 LP 费率覆盖值(仅当池子开启动态费率时生效)

BeforeSwapDelta 是int256 的别名,高 128 位是 specified token 的 delta(即用户指定数量的那个 token),低 128 位是 unspecified token 的 delta:

需要注意,BeforeSwapDelta 的语义是Hook 收取费用应当返回正值,Hook 退还代币应当返回负值。开发者很容易把符号搞反;同时,specified 与 unspecified 的对应关系取决于 params.zeroForOne 与amountSpecified 的正负,写法稍有不慎就会出现 token 错位。

PoolManager 会将 beforeSwap 返回的specifiedDelta 直接累加到amountToSwap 上:

这一行隐含了一个关键语义:Hook 可以截留 swap 数额。当hookDeltaSpecified 等于-params.amountSpecified 时,amountToSwap 直接归零,相当于 Hook 完全接管了这笔 swap——这就是所谓的 Async Hook 或Custom Curve Hook。

Async Hook 是 v4 中风险最高的一类设计模式:它本质上是用 Hook 自己的逻辑替换了 Uniswap 的 swap 逻辑。如果 Hook 存在漏洞或本身就是恶意的,用户资金将不再受到Uniswap 原生定价逻辑的约束,而主要依赖 Hook 自身实现的正确性。

3.3 Delta 结算与 NonzeroDeltaCount

beforeSwap 和afterSwap 返回的 delta 不会立即触发转账,而是被记录到 PoolManager 的内部账本中:

每当一笔 token 的累计 delta 从零变为非零,NonzeroDeltaCount 自增;变回零时自减。如 2.1 所述,unlock() 结束时若NonzeroDeltaCount != 0,整笔交易 revert。

Hook 通过 settle()(向 PoolManager 转账)和 take()(从 PoolManager 取出)两个动作平衡自己的 delta:

这套机制带来的安全语义是清晰的:所有人最终都必须把账平掉。但它只保证了”账目守恒”,并不保证”账目正确”。如果 Hook 在 beforeSwap 中返回了一个被恶意构造的 delta,PoolManager 会忠实地按这个 delta 记账,只要最终被 settle 平掉,交易就成功——即使这意味着Hook 可以通过伪造业务状态,使系统错误地认为攻击者拥有某些资产权益,而 PoolManager 无法识别这种业务层面的错误。

此前 Cork Protocol 安全事件便是因为其Hook存在漏洞,而在被攻击前它已经经过了四家审计公司的审计。事后复盘我们发现:

- 四家审计中有三家的 scope 不包含 CorkHook 合约

- 唯一审计了 CorkHook 的一家识别出了部分代码问题并提交了改进建议,但未完全覆盖到访问控制问题

- 另有一家审计方在其报告中明确建议:“an interesting follow-up engagement would be to prove the invariants for the CorkHook functions that are being invoked by different components verified within the scope of this engagement”。这一建议从事后复盘角度看具有较强针对性。

这暴露出 v4 Hook 时代一个新的审计盲区:协议复杂度的爆炸式增长导致 scope 划定本身成为一个安全决策。Hook 与协议其他合约的交互链路非常长,单独审 Hook 合约不足以发现跨合约的组合性问题;反过来,审周边合约而把 Hook 放在 scope 外,则会漏掉 v4 时代最大的攻击面。

4. 反思

把协议机制和 Cork 攻击复盘并列来看,可以归纳出 v4 Hook 安全模型的几个核心要点:

(1) 如果Hook 回调函数依赖 PoolManager 提供的调用上下文,应显式限制仅由 PoolManager 调用。BaseHook 不会替开发者做这件事,这是 v4 与一般合约审计经验最容易冲突的设计陷阱

(2) Hook 与池子的绑定关系不受 PoolManager 限制。开发者必须自行在beforeInitialize 中实现池子白名单或单池绑定

(3) Hook 地址的权限位必须与函数实现严格一致。计算出的地址应当把未来可能扩展的所有权限位预先包含进去

(4) Async / Custom Curve Hook 本质上是完全自定义的 swap 实现。它没有任何 Uniswap 协议层面的保护,必须按”完全自治的金融合约”标准审计

(5) Delta 会计的”守恒”不等于”正确”。NonzeroDeltaCount == 0 只能保证账本最终平衡,不能保证账本的内容未被恶意操纵

(6) 跨市场的代币类型混淆是 v4 时代的新型攻击面。当协议允许用户创建市场时,对代币的语义校验是必须的,不能仅依赖接口检查

每个 Hook 都是一个独立的信任域,每个池子的安全性由其绑定的 Hook 决定。Hook 安全审计的复杂度因此也不再是”审一份代码”,而是”审一个完整的子协议”——这一变化对项目方和审计方都意味着方法论上的升级。

查看原文

熱門幣種推薦

相關問答

QUniswap v4 Hook 机制相比 v3 最主要的架构变化是什么?

AUniswap v4 相比 v3 最主要的架构变化是引入了 Hook(钩子)机制。它允许开发者将自定义合约挂载到流动性池的生命周期事件(如 swap、加减流动性、初始化等关键节点)上,从而注入任意逻辑,将 AMM 从一个固定金融原语拓展为可编程的金融基础设施。

Q在 Uniswap v4 中,Hook 的权限是如何确定的?如果权限位与函数实现不一致会导致什么问题?

AHook 的权限由其部署地址的低14个比特位决定。每个位对应一个生命周期回调(如 beforeSwap),PoolManager 通过检查这些位来判断是否需要调用对应的 Hook 函数。如果权限位与函数实现不一致,会导致两类问题:1) 实现了某函数但地址未编码对应权限位,导致该逻辑永远不会被调用;2) 地址编码了某权限位但未实现对应函数,可能导致回调时 revert 或校验失败,造成相关操作无法执行的 DOS 攻击风险。

Q文章中提到 BaseHook 抽象合约存在一个“被普遍忽视的访问控制陷阱”,具体指什么?

A这个陷阱是指,早期版本的 BaseHook 抽象合约只对其中的 unlockCallback() 函数添加了 onlyPoolManager 修饰符进行保护。然而,对于其他 Hook 回调函数(如 beforeSwap、afterSwap、beforeAddLiquidity 等),BaseHook 没有提供任何访问控制保护。这意味着开发者必须自己在这些函数中显式添加访问控制(如验证调用者是否为 PoolManager),否则这些关键函数可能被任意地址调用,造成安全漏洞。

Q什么是 Uniswap v4 中的 Async Hook(或 Custom Curve Hook)?为什么它被认为是风险最高的一类设计模式?

AAsync Hook(或 Custom Curve Hook)是一种 Hook 设计模式,指在 beforeSwap 回调中,Hook 返回的指定代币 delta 值正好等于用户请求交换数额的相反数,导致实际执行 AMM 交换的数额归零。这意味着 Hook 用自己的逻辑完全接管了这笔 swap,取代了 Uniswap 原生的定价和交换逻辑。这种模式风险最高,因为用户资金不再受 Uniswap 协议本身的安全性和定价逻辑保护,其安全性完全依赖于 Hook 合约自身实现的正确性。如果 Hook 存在漏洞或是恶意的,将直接导致用户资金损失。

Q根据文章对 Cork Protocol 安全事件的分析,Uniswap v4 Hook 时代在安全审计方面暴露出什么新的问题或盲区?

ACork Protocol 安全事件暴露出,在 Uniswap v4 Hook 时代,由于协议复杂度爆炸式增长,审计范围的划定本身成为一个关键的安全决策盲区。具体问题包括:1) 审计范围若不包含 Hook 合约本身,会漏掉最大的攻击面;2) 即使审计了 Hook 合约,若未充分考虑其与协议其他合约的长交互链路和组合性问题,也可能发现不了跨合约漏洞;3) 需要采用新的审计方法论,将每个 Hook 视为一个需要独立、全面审计的“完整子协议”,而不仅仅是审查一份孤立的代码。

你可能也喜歡

交易

現貨
合約

熱門文章

如何購買ONE

歡迎來到HTX.com!在這裡,購買Harmony (ONE)變得簡單而便捷。跟隨我們的逐步指南,放心開始您的加密貨幣之旅。第一步:創建您的HTX帳戶使用您的 Email、手機號碼在HTX註冊一個免費帳戶。體驗無憂的註冊過程並解鎖所有平台功能。立即註冊第二步:前往買幣頁面,選擇您的支付方式信用卡/金融卡購買:使用您的Visa或Mastercard即時購買Harmony (ONE)。餘額購買:使用您HTX帳戶餘額中的資金進行無縫交易。第三方購買:探索諸如Google Pay或Apple Pay等流行支付方式以增加便利性。C2C購買:在HTX平台上直接與其他用戶交易。HTX 場外交易 (OTC) 購買:為大量交易者提供個性化服務和競爭性匯率。第三步:存儲您的Harmony (ONE)購買Harmony (ONE)後,將其存儲在您的HTX帳戶中。您也可以透過區塊鏈轉帳將其發送到其他地址或者用於交易其他加密貨幣。第四步:交易Harmony (ONE)在HTX的現貨市場輕鬆交易Harmony (ONE)。前往您的帳戶,選擇交易對,執行交易,並即時監控。HTX為初學者和經驗豐富的交易者提供了友好的用戶體驗。

655 人學過發佈於 2024.12.12更新於 2026.06.02

如何購買ONE

相關討論

歡迎來到 HTX 社群。在這裡,您可以了解最新的平台發展動態並獲得專業的市場意見。 以下是用戶對 ONE (ONE)幣價的意見。

活动图片