安全与风控
开发 Uniswap v4 Hook 时的安全防护与审计要点
在编写或审计 Uniswap v4 hook 时,按安全优先流程防范资金被盗、权限滥用与会计错误。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
开发 Uniswap v4 Hook 时特定
解决思路
- 先建立威胁模型:调用者验证(只有 PoolManager 能调用 hook 函数,验证 msg.sender == address(poolManager))、发送者身份(msg.sender 永远是 PoolManager,不是终端用户,用 sender 参数识别用户)、Router 上下文(sender 参数标识的是 router 而非用户,需实现 router 白名单)、状态暴露(hook 状态在交易执行中途可被读取,避免在链上存储敏感数据)、重入面(hook 的外部调用可引发重入,使用重入锁并尽量减少外部调用)。
- 按权限标志风险矩阵评估 14 个 hook 权限:beforeInitialize(LOW,验证池参数)、afterInitialize(LOW,可安全初始化状态)、beforeAddLiquidity(MEDIUM,可能阻止合法 LP)、afterAddLiquidity(LOW,可安全追踪/奖励)、beforeRemoveLiquidity(HIGH,可能困住用户资金)、afterRemoveLiquidity(LOW,可安全追踪)、beforeSwap(HIGH,可能操纵价格)、afterSwap(MEDIUM,可观察最终状态)、beforeDonate(LOW,仅访问控制)、afterDonate(LOW,可安全追踪)、beforeSwapReturnDelta(CRITICAL,NoOp 攻击向量)、afterSwapReturnDelta(HIGH,可提取价值)、afterAddLiquidityReturnDelta(HIGH,可能少给 LP)、afterRemoveLiquidityReturnDelta(HIGH,可窃取资金)。
- 识别并防范 NoOp Rug Pull 攻击:BEFORE_SWAP_RETURNS_DELTA(bit 10)最危险,恶意 hook 可返回 delta 声称已处理整个 swap,PoolManager 接受并结算,hook 保留全部输入代币而不提供输出,用户损失全部 swap 金额。
- 与任何启用 beforeSwapReturnDelta: true 的 hook 交互前:审计 hook 代码验证合法用途、检查所有权(是否可升级、由谁控制)、核实审计记录(是否由知名机构审计)、先用最小金额测试。
- 注意 NoOp 模式的合法用途:即时流动性(JIT)、自定义 AMM 曲线、基于意图的交易系统、RFQ/PMM 集成,但每种都需谨慎实现与审计。
- 掌握 Delta 会计基础:核心不变量为每笔交易 sum(deltas) == 0;关键函数 take(currency, to, amount) 从 PoolManager 提取代币、settle(currency) 向 PoolManager 支付代币、sync(currency) 更新 PoolManager 余额追踪为 settle 做准备。
- 使用正确的结算模式:先 sync,再 transfer,最后 settle,即 poolManager.sync(currency); currency.transfer(address(poolManager), amount); poolManager.settle(currency);。
- 避免常见结算错误:忘记 sync 导致结算失败、顺序错误(必须 sync → transfer → settle)、部分结算使交易处于无效状态、重复结算导致会计错误。
- 在每个 hook 回调上实现 onlyPoolManager 修饰器:require(msg.sender == address(poolManager), "Not PoolManager"),否则任何人都能直接调用 hook 函数、操纵状态、通过伪造回调盗取资金。
- 对需要用户身份的 hook 实现 router 白名单:用 mapping(address => bool) public allowedRouters,在回调中 require(allowedRouters[sender], "Router not allowed")。
- 通过 hookData 获取用户身份:address user = abi.decode(hookData, (address)),但需注意 router 必须可信以提供准确的用户地址。
- 避免 msg.sender 陷阱:hook 中 msg.sender 永远是 PoolManager,不要用 msg.sender == someUser 判断用户,应使用 sender 参数。
- 处理非标准代币风险:fee-on-transfer(收到金额小于发送金额,需测量实际余额变化)、rebasing(余额无转账也会变化,避免存储原始余额)、ERC-777(转账回调引发重入,使用重入锁)、pausable(转账可被阻止,优雅处理失败)、blocklist(特定地址被封锁,用生产地址测试)、低精度(计算精度损失,使用合适缩放)。
- 使用安全余额检查模式:先记录 balanceBefore = token.balanceOf(address(this)),再 safeTransferFrom,最后 received = token.balanceOf(address(this)) - balanceBefore。
- 从 BaseHook 模板开始,所有权限默认关闭,只启用需要的:getHookPermissions 中 beforeInitialize、afterInitialize、beforeAddLiquidity、afterAddLiquidity、beforeRemoveLiquidity、afterRemoveLiquidity、beforeSwap、afterSwap、beforeDonate、afterDonate、beforeSwapReturnDelta、afterSwapReturnDelta、afterAddLiquidityReturnDelta、afterRemoveLiquidityReturnDelta 全部设为 false,仅实现已启用的回调。
- 部署前完成安全清单:1) 所有 hook 回调验证 msg.sender == poolManager;2) 如需要则实现 router 白名单;3) 无可能导致 OOG 的无界循环;4) 外部调用加重入锁;5) Delta 会计求和为零;6) 处理 fee-on-transfer 代币;7) 无硬编码地址;8) 遵守滑点参数;9) 不在链上存储敏感数据;10) 升级机制安全(如适用);11) 如启用 beforeSwapReturnDelta 需有正当理由;12) 完成模糊测试;13) 完成不变量测试。
- 遵守各回调的 gas 预算:beforeSwap < 50,000 gas(硬上限 150,000)、afterSwap < 30,000 gas(硬上限 100,000)、beforeAddLiquidity < 50,000 gas(硬上限 200,000)、afterAddLiquidity < 30,000 gas(硬上限 100,000)、beforeRemoveLiquidity < 50,000 gas(硬上限 200,000)、afterRemoveLiquidity < 30,000 gas(硬上限 100,000)、含外部调用的回调 < 100,000 gas(硬上限 300,000)。
- 避免常见 gas 陷阱:无界循环(遍历动态数组会超区块 gas 上限,限制数组大小或分页)、热路径中的 SSTORE(每个新存储槽约 20,000 gas,用 transient storage tstore/tload 存储不跨交易持久的数据,需 Solidity >= 0.8.24 且 EVM 目标为 cancun 或更高)、外部调用(每次跨合约调用约 2,600 gas 基础成本加被调用方执行成本,尽量批量调用)、字符串操作(回调中避免 string 操作,用 bytes32 作标识符)、冗余读取(缓存 poolManager 调用,重复 getSlot0() 或 getLiquidity() 每次都耗 gas)。
- 用 Foundry 测量 gas:forge test --match-test test_beforeSwapGas --gas-report 分析特定回调;forge snapshot --match-contract MyHookTest 快照所有测试的 gas 使用。
- 计算 hook 风险评分(0-33):权限(0-14,已启用权限风险等级之和)、外部调用(0-5,外部交互数量与类型)、状态复杂度(0-5,可变状态量)、升级机制(0-5,代理、admin 函数等)、代币处理(0-4,非标准代币支持)。
- 按评分选择审计层级:0-5 低风险(自审 + 同行评审)、6-12 中风险(建议专业审计)、13-20 高风险(必须专业审计)、21-33 极高风险(需要多次审计)。
- 绝对禁止事项:1) 不要用 msg.sender 判断用户身份(永远是 PoolManager);2) 不理解 NoOp 攻击就不要启用 beforeSwapReturnDelta;3) 不要在链上存储密码、密钥或 PII;4) 不要用 transfer() 发送 ETH,用 call{value:}("");5) 不要假设代币精度,始终查询代币;6) 不要用 block.timestamp 作随机数;7) 不要在调用中硬编码 gas 限制;8) 不要忽略外部调用的返回值;9) 不要用 tx.origin 做授权(钓鱼向量,恶意合约可用原始用户的 tx.origin 转发调用)。
- 完成部署前审计清单:1) 安全导向开发者代码审查(所有 hook);2) 所有回调的单元测试(所有 hook);3) Foundry 模糊测试(所有 hook);4) 不变量测试(含 delta 返回的 hook);5) 主网 fork 测试(所有 hook);6) gas 分析(所有 hook);7) 形式化验证(关键 hook);8) Slither/Mythril 分析(所有 hook);9) 外部审计(中风险及以上 hook);10) 漏洞赏金计划(高风险及以上 hook);11) 监控/告警设置(所有生产 hook)。
- 参考已审计的生产 hook 学习安全特性:Flaunch(代币发行平台,多签 admin、时间锁)、EulerSwap(借贷集成,按市场隔离风险)、Zaha TWAMM(时间加权 AMM,渐进执行降低 MEV)、Bunni(LP 管理,集中流动性防护)。
适用边界 · 注意事项
- 不开发区块链智能合约或与 Uniswap v4 无关的场景
- 没有 Solidity 与 Foundry 基础、无法理解 delta 会计与重入概念的开发者
- 仅做只读查询、不涉及资金托管或 hook 回调的集成场景