当前位置: 首页 > news >正文

uniswap v4 hooks标志位

hooks的代码位置在这,它是是组织校验,调用用户创建钩子的类库。

首先看其中定义的常量:

    uint160 internal constant ALL_HOOK_MASK = uint160((1 << 14) - 1);uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13;uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12;uint160 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11;uint160 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10;uint160 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9;uint160 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8;uint160 internal constant BEFORE_SWAP_FLAG = 1 << 7;uint160 internal constant AFTER_SWAP_FLAG = 1 << 6;uint160 internal constant BEFORE_DONATE_FLAG = 1 << 5;uint160 internal constant AFTER_DONATE_FLAG = 1 << 4;uint160 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3;uint160 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2;uint160 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1;uint160 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0;

我们知道IHooks 接口中定义了 10 个核心方法,分别对应上面的标志位:

标志位描述对应的接口方法
BEFORE_INITIALIZE_FLAG初始化前触发beforeInitialize
AFTER_INITIALIZE_FLAG初始化后触发afterInitialize
BEFORE_ADD_LIQUIDITY_FLAG添加流动性前触发beforeAddLiquidity
AFTER_ADD_LIQUIDITY_FLAG添加流动性后触发afterAddLiquidity
BEFORE_REMOVE_LIQUIDITY_FLAG移除流动性前触发beforeRemoveLiquidity
AFTER_REMOVE_LIQUIDITY_FLAG移除流动性后触发afterRemoveLiquidity
BEFORE_SWAP_FLAG交换前触发beforeSwap
AFTER_SWAP_FLAG交换后触发afterSwap
BEFORE_DONATE_FLAG捐赠前触发beforeDonate
AFTER_DONATE_FLAG捐赠后触发afterDonate

这些标志位直接对应 IHooks 接口中的方法,用于标识 Hook 合约是否支持这些方法。

RETURNS_DELTA_FLAG

除了上述 10 个标志位,还有 4 个额外的标志位用于标识扩展功能。这些标志位与返回 Delta 值 的能力相关:

标志位描述扩展功能
BEFORE_SWAP_RETURNS_DELTA_FLAG交换前返回 Delta 值返回 BeforeSwapDelta
AFTER_SWAP_RETURNS_DELTA_FLAG交换后返回 Delta 值返回 int256 Delta 值
AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG添加流动性后返回 Delta 值返回 BalanceDelta
AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG移除流动性后返回 Delta 值返回 BalanceDelta

这几个RETURNS_DELTA标志位用于标识 Hook 合约是否支持在特定操作后返回一个 Delta 值。Delta 值通常表示某种状态的变化,例如交易金额的调整、流动性变化或其他相关的数值。

举个例子,假设我们有一个 Hook 合约,它在交易(swap)之前动态调整交易金额。这个 Hook 合约会返回一个 Delta 值,用于修改交易的输入金额。
 

contract ExampleHook is IHooks {function beforeSwap(address sender,PoolKey calldata key,IPoolManager.SwapParams calldata params,bytes calldata hookData) external returns (bytes4, int256 delta) {// 返回一个 Delta 值,用于调整交易金额delta = params.amountSpecified / 10; // 调整金额的 10%return (this.beforeSwap.selector, delta);}
}

此时我们创建合约的时候就要将BEFORE_SWAP_RETURNS_DELTA_FLAG设置为1。

这个时候hooks库在调用用户钩子的beforeSwap方法后,就可以从Hook 返回的数据中解析出 Delta 值并正确调整流动性提供者的余额。我们可以看到hooks库的beforeSwap方法对于BEFORE_SWAP_RETURNS_DELTA_FLAG的运用。

function beforeSwap(IHooks self, PoolKey memory key, IPoolManager.SwapParams memory params, bytes calldata hookData)internalreturns (int256 amountToSwap, BeforeSwapDelta hookReturn, uint24 lpFeeOverride)
{amountToSwap = params.amountSpecified;if (self.hasPermission(BEFORE_SWAP_FLAG)) {bytes memory result = callHook(self, abi.encodeCall(IHooks.beforeSwap, (msg.sender, key, params, hookData)));// 检查返回数据的长度是否符合预期if (result.length != 96) InvalidHookResponse.selector.revertWith();// 如果启用了 RETURNS_DELTA 标志位,则解析 Delta 值if (self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) {hookReturn = BeforeSwapDelta.wrap(result.parseReturnDelta());// 使用 Delta 值调整交易金额int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta();if (hookDeltaSpecified != 0) {bool exactInput = amountToSwap < 0;amountToSwap += hookDeltaSpecified;// 确保交易类型(exact input/output)不发生变化if (exactInput ? amountToSwap > 0 : amountToSwap < 0) {HookDeltaExceedsSwapAmount.selector.revertWith();}}}}
}

如果我们在 Hooks 合约中,改变了输入的交易金额但是有并没有返回delta值,或者返回了delta值但是没有设置相应的RETURNS_DELTA_FLAG,则会导致没有调整预期的状态,致使交易失败。所以当我们在钩子中动态调整了交易金额,则务必返回delta,并设置相应的RETURNS_DELTA_FLAG。

我们看一下Hooks类库中的hasPermission方法:

    function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) {return uint160(address(self)) & flag != 0;}

钩子合约的发布

可以看出对标志位的校验是通过用户钩子合约的地址进行的,也就是说,当我们发布一个hooks,其地址承载力此钩子实现了那些方法的标志。主要逻辑我们可以参考一下DeployHooks的脚本文件:

这里演示了用户如何发布一个自定义钩子合约。

pragma solidity ^0.8.19;import "forge-std/Script.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {HookMiner} from "../src/utils/HookMiner.sol";/// @dev Replace import with your own hook
import {MockCounterHook} from "../test/mocks/MockCounterHook.sol";/// @notice Mines the address and deploys the Counter.sol Hook contract
contract DeployHookScript is Script {address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C);/// @dev Replace with the desired PoolManager on its corresponding chainIPoolManager constant POOLMANAGER = IPoolManager(address(0xE03A1074c86CFeDd5C142C4F04F1a1536e203543));function setUp() public {}function run() public {// 钩子合约的地址必须编码特定的标志。uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG| Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG);bytes memory constructorArgs = abi.encode(POOLMANAGER);// Mine a salt that will produce a hook address with the correct flags(address hookAddress, bytes32 salt) =HookMiner.find(CREATE2_DEPLOYER, flags, type(MockCounterHook).creationCode, constructorArgs);// Deploy the hook using CREATE2vm.broadcast();MockCounterHook counter = new MockCounterHook{salt: salt}(IPoolManager(POOLMANAGER));require(address(counter) == hookAddress, "CounterScript: hook address mismatch");}
}

我们通过修改flags后面的标志位来设置自定义合约支持的钩子方法。

接下来会通过HookMiner.find方法计算符合用户自定义标志位的地址和salt

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";/// @title HookMiner
/// @notice a minimal library for mining hook addresses
library HookMiner {// mask to slice out the bottom 14 bit of the addressuint160 constant FLAG_MASK = Hooks.ALL_HOOK_MASK; // 0000 ... 0000 0011 1111 1111 1111// Maximum number of iterations to find a salt, avoid infinite loops or MemoryOOG// (arbitrarily set)uint256 constant MAX_LOOP = 160_444;/// @notice Find a salt that produces a hook address with the desired `flags`/// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address/// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy)/// @param flags The desired flags for the hook address. Example `uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | ...)`/// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode`/// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))`/// @return (hookAddress, salt) The hook deploys to `hookAddress` when using `salt` with the syntax: `new Hook{salt: salt}(<constructor arguments>)`function find(address deployer, uint160 flags, bytes memory creationCode, bytes memory constructorArgs)internalviewreturns (address, bytes32){flags = flags & FLAG_MASK; // mask for only the bottom 14 bitsbytes memory creationCodeWithArgs = abi.encodePacked(creationCode, constructorArgs);address hookAddress;for (uint256 salt; salt < MAX_LOOP; salt++) {hookAddress = computeAddress(deployer, salt, creationCodeWithArgs);// if the hook's bottom 14 bits match the desired flags AND the address does not have bytecode, we found a matchif (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) {return (hookAddress, bytes32(salt));}}revert("HookMiner: could not find salt");}/// @notice Precompute a contract address deployed via CREATE2/// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address/// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy)/// @param salt The salt used to deploy the hook/// @param creationCodeWithArgs The creation code of a hook contract, with encoded constructor arguments appended. Example: `abi.encodePacked(type(Counter).creationCode, abi.encode(constructorArg1, constructorArg2))`function computeAddress(address deployer, uint256 salt, bytes memory creationCodeWithArgs)internalpurereturns (address hookAddress){return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xFF), deployer, salt, keccak256(creationCodeWithArgs))))));}
}

方法并不难,就是不断地调整salt生成address,直到address的地位,符合用户设置的flag为止。

再看下发布合约的最后一步:

vm.broadcast();
MockCounterHook counter = new MockCounterHook{salt: salt}(IPoolManager(POOLMANAGER));

这里的new MockCounterHook{salt: salt}是solidity的语法糖,这么写会隐式的调用create2方法创建合约,而 Foundry 的脚本中,默认情况下操作是模拟执行的,并不会发布到链上。在调用 vm.broadcast(); 之后,所有的合约调用或部署操作都会被视为真实交易,并广播到链上。

 

相关文章:

  • set autotrace报错
  • 模型部署——cuda编程入门
  • SpringMVC——第五章:视图View
  • qml显示视频帧(QQuickImageProvider)
  • 58认知干货:创业经验分享及企业形式的汇总
  • 【操作系统】深入理解内存管理:从虚拟内存到OOM Killer
  • 从实列中学习linux shell12 通过Shell脚本来优化MySQL数据库性能,特别是慢SQL跟踪和索引优化
  • Java学习手册:MyBatis 框架作用详解
  • 【LLM】deepseek R1之GRPO训练笔记(持续更新)
  • Axure打开html文件失败,解决方案:
  • Three.js在vue中的使用(二)-动画、材质
  • 微服务框架选型
  • 小白机器人假想:分布式关节控制——机器人运动的未来模式?
  • 数字化时代下,软件测试中的渗透测试是如何保障安全的?
  • C# 方法(返回值、返回语句和void方法)
  • spring cloud 与 cloud alibaba 版本对照表
  • HTML04:图像标签
  • 组合模式(Composite Pattern)
  • 【计算机网络】HTTP中GET和POST的区别是什么?
  • 工业大模型:从设备诊断到工艺重构
  • 演员扎堆音乐节,是丰富了舞台还是流量自嗨?
  • 9金收官!跳水世界杯总决赛朱子锋、程子龙包揽男子10米台冠亚军
  • 习近平给谢依特小学戍边支教西部计划志愿者服务队队员的回信
  • 哈马斯:愿与以色列达成为期5年的停火协议
  • 王毅在金砖正式成员和伙伴国外长会上的发言
  • 魔都眼|西岸国际咖啡生活节:连接艺术、音乐与宠物