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)
    internal
    returns (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 chain
    IPoolManager 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 CREATE2
        vm.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 address
    uint160 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)
        internal
        view
        returns (address, bytes32)
    {
        flags = flags & FLAG_MASK; // mask for only the bottom 14 bits
        bytes 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 match
            if (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)
        internal
        pure
        returns (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(); 之后,所有的合约调用或部署操作都会被视为真实交易,并广播到链上。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值