这篇文章详细介绍了如何构建 Uniswap V4 hooks,包括所需的核心库、逐步的开发指南(附带代码示例),以及在开发过程中需要考虑的重要安全和性能问题。它旨在帮助开发者理解并实现自定义的流动性池行为,涵盖了从基础设置到复杂安全考量的全面内容。
你好,勇敢的DeFi探索者!你一定听说过这些新潮的 Uniswap V4 hooks 并心想,“嘿,我或许也能创造一个!” 那么,系好安全带,因为我们即将踏上激动人心的定制化流动性池行为冒险之旅。别担心,我将成为你值得信赖的向导,我们将一同应对这次旅程!
在我们深入代码之前,先聊聊这次旅行我们要准备什么。你可能会问,“我需要什么才能开始呢?” 问得好!这是我们的基本装备:
把它想象成你的瑞士军刀——一种多功能工具,包含了定义你的hook能做的一切。
BEFORE_SWAP_FLAG、AFTER_SWAP_FLAG),以及用于验证和调用 hooks 的函数。它们就像你的地图和指南针。它们能帮助你导航广阔的流动性池景观。
PoolKey 是一个唯一标识池的结构体,而 PoolId 是 PoolKey 的一种更节省 Gas 的表示。这是你的通用翻译器,帮助你与原生代币(如 ETH)和 ERC20 代币进行沟通。
把它看作你的帐篷——一个坚实的基础,可以在智能合约开发的恶劣环境中为你提供庇护。
“等等,我真的需要所有这些吗?”你可能会问。相信我,每一部分都在你的 hook 创建冒险中扮演着关键角色。让我们搭建好营地,看看它们是如何协同工作的!
首先,让我们设置好我们的合约。这是我们将要开始的基本结构:
1import {BaseHook} from "@uniswap/v4-periphery/contracts/BaseHook.sol";
2import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
3import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
4import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
5import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol";
6import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol";
7import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
8
9contract MyAwesomeHook is BaseHook {
10 using StateLibrary for IPoolManager;
11 using PoolIdLibrary for PoolKey;
12 using CurrencyLibrary for Currency;
13 using FixedPointMathLib for uint256;
14
15 constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
16
17 // 更多精彩内容即将到来!
18}
让我们来分析一下这些 using 语句并理解它们的重要性:
using StateLibrary for IPoolManager; — 这使得与池状态的交互更加高效。它允许你直接在 IPoolManager 对象上调用 StateLibrary 函数,从而简化了查询池流动性或交易数据等操作。using PoolIdLibrary for PoolKey; — 这有助于在 PoolKey 结构体和 PoolId 之间轻松转换。对于需要识别特定池的操作至关重要,例如将 hook 定位到特定的交易对。using CurrencyLibrary for Currency; — 此语句增强了你统一处理不同代币类型的能力。它允许你直接在 Currency 对象上使用 CurrencyLibrary 函数,简化了代币转账、余额检查以及其他与代币相关的操作。using FixedPointMathLib for uint256; — 这提供了 DeFi 应用程序所需精确数学运算的访问权限。它允许你对 uint256 数字执行定点算术,这对于准确的价格计算、费用计算以及其他金融操作至关重要。这些 using 语句是编写高效、可读且 gas 优化的 hook 代码的关键。它们提供了语法糖(syntactic sugar),让你在使用这些复杂的 DeFi 概念时能够编写更直观的代码。
接下来,我们需要告诉 Uniswap 我们的 hook 能做什么。这就像为你的露营活动填写许可证一样:
1function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
2 return Hooks.Permissions({
3 beforeInitialize: false,
4 afterInitialize: false,
5 beforeAddLiquidity: false,
6 afterAddLiquidity: false,
7 beforeRemoveLiquidity: false,
8 afterRemoveLiquidity: false,
9 beforeSwap: false,
10 afterSwap: false,
11 beforeDonate: false,
12 afterDonate: false,
13 beforeSwapReturnDelta: false,
14 afterSwapReturnDelta: false,
15 afterAddLiquidityReturnDelta: false,
16 afterRemoveLiquidityReturnDelta: false
17 });
18}
现在,你可能会挠头想,“等一下,我们真的需要这个函数吗?我以为 Uniswap 是根据合约地址来判断使用哪些 hook 的呢。”
嗯,你完全正确,你这个聪明的露营者!Uniswap 确实是根据合约地址来决定调用哪个 hook 的。这个 getHookPermissions() 函数更像是我们人类的“视觉辅助”。它帮助开发者(比如我们)快速了解 hook 应该做什么,而无需深入研究 地址挖掘 的细节。
你可能会问,“那么,我应该费心实现它吗?” 答案是肯定的!虽然它对 Uniswap 的运行并非严格必要,但对于其他可能使用你代码的开发者(包括未来的你自己)来说,它非常有帮助。
要使用 hook,你需要将相应的权限设置为 true。例如,如果你希望你的 hook 在交易前后执行某些操作,你会将 beforeSwap 和 afterSwap 设置为 true。
Uniswap 如何从合约地址读取这些权限的复杂性是一个引人入胜的话题,但这有点像高级野外生存技术——我们把它留到下一次探险。现在,让我们专注于搭建好我们的基本营地!
现在到了有趣的部分——让我们添加一些自定义行为!我们将实现 beforeSwap 函数:
1function beforeSwap(
2 address sender,
3 PoolKey calldata key,
4 IPoolManager.SwapParams calldata params,
5 bytes calldata data
6)
7 external
8 override
9 returns (bytes4)
10{
11 // 你的魔法在此!
12 console.log("Ooh, a swap is about to happen from", sender);
13
14 // 别忘了这部分!
15 return BaseHook.beforeSwap.selector;
16}
“那个 return 语句是怎么回事?”你问。好眼力!那是我们告诉 Uniswap,“是的,我们完成了我们的工作,你现在可以继续了。”
恭喜你,勇敢的开发者!你刚刚创建了你的第一个 Uniswap V4 hook。但是你如何实际使用它呢?问得好!
beforeSwap 函数就会运行!在我们结束冒险之前,让我们谈谈在开发 hook 时需要牢记的一些重要注意事项。即使在早期阶段,也务必了解潜在的陷阱和安全风险:
Gas 效率 — 在以太坊的世界里,每一次计算都会消耗 Gas。请注意你的 hook 的复杂性。效率低下的代码会使你的 hook 使用成本过高。
重入风险 — 如果你的 hook 与外部合约交互,请警惕重入攻击(reentrancy attacks)。始终遵循检查-效果-交互模式(checks-effects-interactions pattern)。
访问控制 — 确保你的 hook 中敏感功能只能由授权地址调用。Uniswap V4 提供了一些内置保护,但保持警惕仍然很重要。Cork Protocol 中发现的一个关键漏洞——它使用了类似 V4 的 hook 架构——就是由于 缺乏访问控制,允许攻击者直接调用 hook 的函数。
状态管理 — 请记住,hook 被设计为在调用之间是无状态(stateless)的。如果你需要维护状态,请小心操作,并考虑对 Gas 成本和潜在攻击向量的影响。
小数精度 — 在处理代币数量和价格时,要特别小心小数精度。四舍五入误差可能导致金融计算中出现重大问题。
测试 — 在各种场景下彻底测试你的 hook。Uniswap 提供了一个测试框架——在考虑部署到主网之前广泛使用它。
可升级性 — 考虑你的 hook 是否需要可升级。如果需要,请仔细实施 升级模式 以避免引入漏洞。
MEV 意识 — 请注意,老练的参与者可能会试图利用你的 hook 来获取 MEV(最大可提取价值)。在设计 hook 时要考虑到这一点。
可组合性 — 你的 hook 可能会与其他 DeFi 协议交互。始终考虑更广泛的生态系统和潜在的意外交互。
审计 — 对于任何用于生产的 hook,专业审计是必须的。即使看起来简单的代码也可能存在隐藏的漏洞。
请记住,在 DeFi 中,你通常处理的是真实价值。代码中的一个小错误可能会导致重大的财务损失。始终谨慎行事,永远不要停止学习安全最佳实践。
为了帮助你快速上手,这里是你的第一个 Uniswap V4 hook 的完整模板。你可以将其复制并粘贴到你的环境中作为起点:
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.19;
3
4import {BaseHook} from "@uniswap/v4-periphery/contracts/BaseHook.sol";
5import {Hooks} from "@uniswap/v4-core/contracts/libraries/Hooks.sol";
6import {IPoolManager} from "@uniswap/v4-core/contracts/interfaces/IPoolManager.sol";
7import {PoolKey} from "@uniswap/v4-core/contracts/types/PoolKey.sol";
8import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/contracts/types/PoolId.sol";
9import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol";
10import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol";
11
12contract MyAwesomeHook is BaseHook {
13 using PoolIdLibrary for PoolKey;
14 using CurrencyLibrary for Currency;
15 using FixedPointMathLib for uint256;
16
17 constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
18
19 function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
20 return Hooks.Permissions({
21 beforeInitialize: false,
22 afterInitialize: false,
23 beforeAddLiquidity: false,
24 afterAddLiquidity: false,
25 beforeRemoveLiquidity: false,
26 afterRemoveLiquidity: false,
27 beforeSwap: true,
28 afterSwap: false,
29 beforeDonate: false,
30 afterDonate: false,
31 beforeSwapReturnDelta: false,
32 afterSwapReturnDelta: false,
33 afterAddLiquidityReturnDelta: false,
34 afterRemoveLiquidityReturnDelta: false
35 });
36 }
37
38 function beforeSwap(
39 address sender,
40 PoolKey calldata key,
41 IPoolManager.SwapParams calldata params,
42 bytes calldata data
43 )
44 external
45 override
45 returns (bytes4)
46 {
47 // 你的自定义逻辑在这里
48 console.log("交易由", sender, "发起");
49
50 return BaseHook.beforeSwap.selector;
51 }
52}
此模板包含了所有必要的导入、基本的合约结构以及 beforeSwap hook 的一个简单实现。你可以在探索更复杂的 hook 行为时修改和扩展它。
恭喜!你刚刚迈出了进入 Uniswap V4 hooks 世界的第一大步。让我们回顾一下你所完成的:
getHookPermissions() 函数,它对于定义 hook 的功能至关重要。beforeSwap 函数作为 hook 逻辑的起点。从这里开始,可能性是巨大的:
beforeSwap 函数。afterSwap 或 beforeAddLiquidity 等其他 hook 函数,以影响交易的不同方面。Uniswap V4 生态系统任你探索和塑造。你的下一步可能会引领 DeFi 的下一个重大突破!
那么,接下来你会构建什么呢?
在 Zealynx,我们深入理解复杂的 AMM 设计,从集中流动性(concentrated liquidity)到新兴的 hook 架构,以及 Uniswap 等协议的安全挑战。无论你是在构建新的 DeFi 协议、审计现有协议,还是需要关于 AMM 项目安全性的专家指导,我们的团队都随时准备提供帮助——请联系我们。
想获取更多像这样的深入分析,保持领先地位吗?订阅我们的时事通讯,确保你不会错过未来的洞察。
getHookPermissions() 的作用是什么,它是否必需?
getHookPermissions() 函数声明了你的合约实现了哪些 hook 回调(例如,beforeSwap、afterSwap)。虽然 Uniswap V4 实际上是从合约的已部署地址的位中确定活动 hook,但此函数充当人类可读的声明,帮助开发者和审计员快速了解 hook 的功能。实现它是最佳实践。msg.sender 是 PoolManager)、由于无边界循环(unbounded loops)导致的基于 Gas 的拒绝服务(denial of service)、当 hook 为多个池服务时的状态管理(state management)错误以及金融计算中的小数精度误差。在任何主网部署之前,强烈建议进行专业审计。本文中使用的关键术语的快速参考:
| 术语 | 定义 |
|---|---|
| Hooks | 在 Uniswap V4 中,在特定池生命周期点执行自定义逻辑的外部智能合约。 |
| Pool Manager | Uniswap V4 中管理所有池、流动性和交易的单例合约(singleton contract)。 |
| Address Mining | 寻找具有特定位标志(bit flags)的部署地址以配置 hook 权限的过程。 |
| MEV | 最大可提取价值(Maximal Extractable Value)——通过重新排序或在区块中插入交易来提取的利润。 |
| Proxy Pattern | 智能合约设计模式,可实现可升级逻辑,同时保留状态和地址。 |
| ERC-6909 | Uniswap V4 中使用的最小多代币接口(Minimal Multi-Token Interface)标准,用于 Gas 高效的代币记账。 |
- 原文链接: zealynx.io/blogs/uniswap...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!