如何在 Uniswap V4 上创建钩子

  • QuickNode
  • 发布于 2024-09-15 13:11
  • 阅读 18

本文介绍了如何在Uniswap V4中使用Hooks添加自定义逻辑,详细讲解了Hooks的生命周期、IHook接口和Hook标志,并提供了一个简单的Swap Limiter Hook的实现和测试方法。

概述

Uniswap V4 引入了 hooks,这是一种在池操作的重要节点添加自定义逻辑的强大方式。Hooks 是与 Uniswap 池一起工作的独立智能合约,允许开发者更改交换行为,创建复杂策略,并在不改变整个协议的情况下实现自定义 AMM 逻辑。

在本指南中,我们将向你展示如何构建一个简单的 hook,以添加基本功能。这将帮助你学习如何开发更复杂的自定义 hooks。让我们开始吧!

你将要做什么

  • 了解 Uniswap V4 Hooks
  • 创建自定义 Hook 智能合约
  • 使用脚本和 Anvil 测试 Hook 智能合约
  • 在测试网上部署和测试 Hook 智能合约

你将需要什么

  • 对以太坊和 DeFi 的经验
  • 已安装 Foundry

Uniswap Hooks

Uniswap V4 hooks 是可以在交换的特定时间插入的智能合约,允许开发者自定义和扩展流动性池的功能。这些 hooks 在设定的时间与主协议协作,允许更多功能而不会使核心合约变得不安全。

关于 Uniswap V4 hooks 的关键点:

  1. Hooks 可以在交换、添加或移除流动性、以及启动池等重要动作之前或之后被触发
  2. 每个 hook 是自己的智能合约,因此你可以单独开发和测试它
  3. 你可以将多个 hooks 一起使用,创建复杂的池行为
  4. 自定义逻辑在交换交易中运行,使用的 gas 少于单独的合约
  5. 开发者可以在不改变主要 Uniswap 协议的情况下添加新的 hooks

Hooks 允许你详细控制池操作。它们可以自定义费用结构,改变流动性分配,甚至在池逻辑中实现复杂的交易策略。通过在交换过程中的关键时刻介入,hooks 可以添加风险控制,使资本使用更高效,甚至帮助在区块链上创建新的金融工具。

以下是你可以使用 hooks 做到的一些示例:

  • 仅在特定条件下发生的订单(如限价订单或止损单)
  • 根据市场条件变化的费用
  • 自动流动性管理策略
  • 不同池之间的套利
  • 创建新的 AMM 曲线或定价方式

这种级别的定制使你能够创建适应特定市场条件或交易需求的池。你可以单独开发和使用 hooks,并将它们组合以创建复杂的池行为,直接在交换过程中运行自定义逻辑以节省 gas。这样的设计使你能够添加通常需要多个交易或区块链外协调的功能。

在接下来的章节中,我们将介绍你需要了解的 hooks 核心概念,如 Hooks 生命周期、IHook 接口和 Hook 标志。

Hook 生命周期

Hooks 可以在多个重要节点与交换过程一起工作:

  • 初始化前:在池启动前运行
  • 初始化后:在池启动后运行
  • 交换前:在交换发生前运行
  • 交换后:在交换发生后运行
  • 添加流动性前:在向池添加流动性前运行
  • 添加流动性后:在向池添加流动性后运行
  • 移除流动性前:在从池中移除流动性前运行
  • 移除流动性后:在从池中移除流动性后运行

通过创建这些 hook 函数,开发者可以在池操作的特定时刻实现自定义行为。

IHook 接口

IHook 接口是你的 hook 合约需要实现的内容。它定义了 Uniswap V4 将在池操作的不同节点调用的函数。以下是它可能的简化版本:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IHook {
    function beforeInitialize(address sender, uint160 sqrtPriceX96) external returns (bytes4);
    function afterInitialize(address sender, uint160 sqrtPriceX96) external returns (bytes4);
    function beforeSwap(address sender, address recipient, bool zeroForOne, uint256 amountSpecified, uint160 sqrtPriceLimitX96) external returns (bytes4);
    function afterSwap(address sender, address recipient, bool zeroForOne, uint256 amountSpecified, uint160 sqrtPriceLimitX96) external returns (bytes4);
    function beforeAddLiquidity(address sender, uint256 amount0, uint256 amount1) external returns (bytes4);
    function afterAddLiquidity(address sender, uint256 amount0, uint256 amount1) external returns (bytes4);
    function beforeRemoveLiquidity(address sender, uint256 amount0, uint256 amount1) external returns (bytes4);
    function afterRemoveLiquidity(address sender, uint256 amount0, uint256 amount1) external returns (bytes4);
}

Hook 标志

当你创建一个 hook 时,你需要指定它实现了哪些函数。这是通过 hook 标志来完成的。这些标志是位标志,指示哪些 hook 函数处于激活状态。例如:

uint160 constant BEFORE_SWAP_FLAG = 1 << 0;
uint160 constant AFTER_SWAP_FLAG = 1 << 1;
uint160 constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 2;
// ... 其他 hook 函数的相应标志

你可以组合这些标志以指示你的 hook 实现了哪些函数。例如,如果你的 hook 实现了 beforeSwap 和 afterSwap,你将使用:

uint160 public constant FLAGS = BEFORE_SWAP_FLAG | AFTER_SWAP_FLAG;

在接下来的部分中,我们将逐步介绍创建一个简单 hook 的过程,展示如何在实践中使用这些工具。

项目先决条件:创建 QuickNode 端点

在进入代码之前,先设置一些先决条件,如获取一个 RPC URL。你可以使用公共节点或自行部署和管理基础设施;但是,如果你希望获得 8 倍的响应时间,你可以将重任交给我们。点击 这里 注册一个免费帐户。

登录 QuickNode 后,单击 创建一个端点 按钮,然后选择 Ethereum 链和 Sepolia 网络。

创建端点后,复制 HTTP 提供者 URL 链接并保留,以便在本地测试网部分使用。

QuickNode 端点

实现你的第一个 Uniswap Hook

现在我们已经介绍了核心概念并完成了先决条件,让我们深入创建你的第一个 Uniswap V4 hook。我们将使用 v4-template 作为起始点,它为 hook 开发提供了坚实的基础。

目录设置

使用 v4-template 作为起始点。你可以通过在 GitHub 仓库上点击 “使用此模板” 来实现,也可以通过克隆来实现:

git clone git@github.com:uniswapfoundation/v4-template.git
cd v4-template

该模板包含一个示例 hook Counter.sol,展示了 beforeSwap()afterSwap() hooks(可以随意先看一眼)。测试模板 Counter.t.sol 预先配置了 v4 池管理器、测试代币和测试流动性,这将有助于测试我们的 hook。

安装依赖

首先,确保你已安装并更新 Foundry:

foundryup

接下来,安装项目依赖:

forge install

虽然现在技术上你可以运行 forge tests 命令,但我们将在添加自己的自定义 hook 逻辑后再进行。

自定义 Hook 合约

现在,让我们创建自己的 hook。我们将实现一个 “Swap Limiter” hook,该 hook 限制单个地址在特定时间框架内可以进行的交换次数。这为池添加了基本的速率限制。

首先,创建一个名为 src/SwapLimiterHook.sol 的文件。然后,打开该文件并包含以下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol";

contract SwapLimiterHook is BaseHook {
    using PoolIdLibrary for PoolKey;

    uint256 public constant MAX_SWAPS_PER_HOUR = 5;
    uint256 public constant HOUR = 3600;

    mapping(address => uint256) public lastResetTime;
    mapping(address => uint256) public swapCount;

    event SwapLimitReached(address indexed user, uint256 timestamp);

    constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}

    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
        return Hooks.Permissions({
            beforeInitialize: false,
            afterInitialize: false,
            beforeAddLiquidity: false,
            afterAddLiquidity: false,
            beforeRemoveLiquidity: false,
            afterRemoveLiquidity: false,
            beforeSwap: true,
            afterSwap: false,
            beforeDonate: false,
            afterDonate: false,
            beforeSwapReturnDelta: false,
            afterSwapReturnDelta: false,
            afterAddLiquidityReturnDelta: false,
            afterRemoveLiquidityReturnDelta: false
        });
    }

    // 强制实施交换限制的主函数
    function beforeSwap(address sender, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
        external
        override
        returns (bytes4, BeforeSwapDelta, uint24)
    {
        uint256 currentTime = block.timestamp;
        if (currentTime - lastResetTime[sender] >= HOUR) {
            swapCount[sender] = 0;
            lastResetTime[sender] = currentTime;
        }

        require(swapCount[sender] < MAX_SWAPS_PER_HOUR, "Swap limit reached for this hour");

        swapCount[sender]++;

        if (swapCount[sender] == MAX_SWAPS_PER_HOUR) {
            emit SwapLimitReached(sender, currentTime);
        }

        return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
    }

    function getRemainingSwaps(address user) public view returns (uint256) {
        if (block.timestamp - lastResetTime[user] >= HOUR) {
            return MAX_SWAPS_PER_HOUR;
        }
        return MAX_SWAPS_PER_HOUR - swapCount[user];
    }
}

这个“Swap Limiter” hook 合约实现了以下功能:

  • 交换限制:它限制每个地址在一小时内最多进行 5 次交换(可以通过 MAX_SWAPS_PER_HOUR 配置)。
  • 基于时间的重置:每个地址的交换计数每小时重置。
  • beforeSwap Hook:此函数检查发送者是否超过了当前小时的交换限制。如果没有,它将增加他们的交换计数。
  • 剩余交换检查:getRemainingSwaps 函数允许用户(或前端)检查他们在当前小时内剩余的交换次数。
  • 事件日志:当用户达到其交换限制时,会发出事件,这对于监控或警告非常有用。

可选地,你可以运行 forge compile 命令来检查你的文件语法是否设置正确。

创建测试文件

现在我们已经创建了我们的 hook,我们需要创建一个测试文件。首先,创建一个名为 test/SwapLimiterHook.t.sol 的文件。然后,包含以下代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {CurrencyLibrary, Currency} from "v4-core/src/types/Currency.sol";
import {PoolSwapTest} from "v4-core/src/test/PoolSwapTest.sol";
import {SwapLimiterHook} from "../src/SwapLimiterHook.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";

import {IPositionManager} from "v4-periphery/src/interfaces/IPositionManager.sol";
import {EasyPosm} from "./utils/EasyPosm.sol";
import {Fixtures} from "./utils/Fixtures.sol";

contract SwapLimiterHookTest is Test, Fixtures {
    using EasyPosm for IPositionManager;
    using PoolIdLibrary for PoolKey;
    using CurrencyLibrary for Currency;
    using StateLibrary for IPoolManager;

    SwapLimiterHook hook;
    PoolId poolId;

    uint256 tokenId;
    int24 tickLower;
    int24 tickUpper;

    event SwapLimitReached(address indexed user, uint256 timestamp);

    function setUp() public {
        // 创建池管理器、实用路由器和测试代币
        deployFreshManagerAndRouters();
        deployMintAndApprove2Currencies();

        deployAndApprovePosm(manager);

        // 在具有正确标志的地址上部署 hook
        address flags = address(
            uint160(Hooks.BEFORE_SWAP_FLAG) ^ (0x4444 << 144) // 命名空间 hook 以避免冲突
        );
        bytes memory constructorArgs = abi.encode(manager);
        deployCodeTo("SwapLimiterHook.sol:SwapLimiterHook", constructorArgs, flags);
        hook = SwapLimiterHook(flags);

        // 创建池
        key = PoolKey(currency0, currency1, 3000, 60, IHooks(hook));
        poolId = key.toId();
        manager.initialize(key, SQRT_PRICE_1_1, ZERO_BYTES);

        // 向池提供全范围流动性
        tickLower = TickMath.minUsableTick(key.tickSpacing);
        tickUpper = TickMath.maxUsableTick(key.tickSpacing);

        (tokenId,) = posm.mint(
            key,
            tickLower,
            tickUpper,
            10_000e18,
            MAX_SLIPPAGE_ADD_LIQUIDITY,
            MAX_SLIPPAGE_ADD_LIQUIDITY,
            address(this),
            block.timestamp,
            ZERO_BYTES
        );
    }

    function testDirectBeforeSwap() public {
        address sender = address(this);
        IPoolManager.SwapParams memory params;
        bytes memory hookData;

        for (uint i = 0; i < 5; i++) {
            (bytes4 selector,,) = hook.beforeSwap(sender, key, params, hookData);
            assertEq(selector, SwapLimiterHook.beforeSwap.selector);
            console.log("Swap %d, Remaining swaps: %d", i + 1, hook.getRemainingSwaps(sender));
        }

        vm.expectRevert("Swap limit reached for this hour");
        hook.beforeSwap(sender, key, params, hookData);
    }

    function testSwapLimiter() public {
        bool zeroForOne = true;
        int256 amountSpecified = -1e18; // 负数表示精确输入交换

        console.log("Initial remaining swaps: %d", hook.getRemainingSwaps(address(this)));

        // 进行 5 次交换(应成功)
        for (uint i = 0; i < 5; i++) {
            // 手动调用 beforeSwap 模拟 hook 触发
            (bytes4 selector,,) = hook.beforeSwap(address(this), key, IPoolManager.SwapParams({zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: 0}), ZERO_BYTES);
            assertEq(selector, SwapLimiterHook.beforeSwap.selector);

            BalanceDelta swapDelta = swap(key, zeroForOne, amountSpecified, ZERO_BYTES);
            assertEq(int256(swapDelta.amount0()), amountSpecified);
            console.log("Swap %d succeeded. Remaining swaps: %d", i + 1, hook.getRemainingSwaps(address(this)));
        }

        // 第 6 次交换应回退
        vm.expectRevert("Swap limit reached for this hour");
        hook.beforeSwap(address(this), key, IPoolManager.SwapParams({zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: 0}), ZERO_BYTES);

        // 尝试第 6 次交换(应失败)
        vm.expectRevert(abi.encodeWithSignature("Wrap__FailedHookCall(address,bytes)", address(hook), abi.encodeWithSignature("Error(string)", "Swap limit reached for this hour")));
        swap(key, zeroForOne, amountSpecified, ZERO_BYTES);

        // 检查剩余交换次数
        uint256 remainingSwaps = hook.getRemainingSwaps(address(this));
        console.log("Final remaining swaps: %d", remainingSwaps);
        assertEq(remainingSwaps, 0, "Should have 0 remaining swaps");
    }

    function testSwapLimitReachedEvent() public {
        address sender = address(this);
        IPoolManager.SwapParams memory params;
        bytes memory hookData;

        for (uint i = 0; i < 4; i++) {
            hook.beforeSwap(sender, key, params, hookData);
        }

        vm.expectEmit(true, false, false, true);
        emit SwapLimitReached(sender, block.timestamp);
        hook.beforeSwap(sender, key, params, hookData);
    }
}

这些测试覆盖了我们 SwapLimiterHook 合约的主要功能,它强制实施交换限制,在小时重置交换计数,并检查用户的剩余交换次数。

让我们回顾一下测试代码中的主要功能。

  • testDirectBeforeSwap 函数:

    • 直接调用 hook 的 beforeSwap 函数 5 次
    • 检查每个调用返回正确的选择器
    • 记录每次调用后的剩余交换次数
    • 期望第六次调用被回退,提示 "Swap limit reached for this hour"
  • testSwapLimiter 函数:

    • 记录初始剩余交换次数
    • 进行 5 次交换,每次:
    • 手动调用 beforeSwap 模拟 hook 触发
    • 执行实际交换
    • 记录成功的交换和剩余交换次数
    • 期望第六次 beforeSwap 调用被回退
    • 期望第六次实际交换被回退并带有包裹的错误消息
    • 检查最终剩余交换次数为 0
  • testSwapLimitReachedEvent 函数:

    • 调用 beforeSwap 4 次
    • 期望 SwapLimitReached 事件在第 5 次调用时被触发
    • 执行第 5 次调用 beforeSwap

使用以下命令执行测试:

forge test

测试结束时你会看到:

Ran 3 tests for test/SwapLimiterHookTest.t.sol:SwapLimiterHookTest
[PASS] testDirectBeforeSwap() (gas: 74596)
[PASS] testSwapLimitReachedEvent() (gas: 58075)
[PASS] testSwapLimiter() (gas: 506599)
Suite result: ok. 3 passed; 0 failed; 0 skipped; finished in 24.54ms (4.06ms CPU time)

如果你想查看 console.log 语句,你可以在测试命令中添加 -vv 标志。可选地,通过多次传递 v 来增加详细程度(例如 -v, -vv, -vvv),以查看更深入的信息(例如,打印执行痕迹)。

根据你的具体实现和你可能添加到 hook 中的任何附加功能,记得根据需要调整测试。

使用 Anvil 和 QuickNode 进行本地开发

为了在更真实的环境中测试你的 hook,你可以使用 Anvil,一个以 QuickNode 为基础的本地测试节点。

首先,在单独的终端窗口中启动 Anvil,并从你的 QuickNode RPC 进行分叉:

anvil --fork-url https://your-quicknode-endpoint.quiknode.pro/your-api-key/

这将启动一个本地测试节点,其状态是你所连接的 QuickNode RPC 当前状态的分叉。

然后更新 Anvil.s.sol 文件,并将现有代码替换为以下内容:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Script.sol";
import "forge-std/console.sol";
import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {PoolManager} from "v4-core/src/PoolManager.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {PoolModifyLiquidityTest} from "v4-core/src/test/PoolModifyLiquidityTest.sol";
import {PoolSwapTest} from "v4-core/src/test/PoolSwapTest.sol";
import {PoolDonateTest} from "v4-core/src/test/PoolDonateTest.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol";
import {Constants} from "v4-core/src/../test/utils/Constants.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {CurrencyLibrary, Currency} from "v4-core/src/types/Currency.sol";
import {SwapLimiterHook} from "../src/SwapLimiterHook.sol";
import {HookMiner} from "../test/utils/HookMiner.sol";

contract SwapLimiterScript is Script {
    address constant CREATE2_DEPLOYER = address(0x4e59b44847b379578588920cA78FbF26c0B4956C);

    function setUp() public {}

    function run() public {
        vm.broadcast();
        IPoolManager manager = deployPoolManager();

        uint160 permissions = uint160(Hooks.BEFORE_SWAP_FLAG);

        (address hookAddress, bytes32 salt) = HookMiner.find(
            CREATE2_DEPLOYER,
            permissions,
            type(SwapLimiterHook).creationCode,
            abi.encode(address(manager))
        );

        vm.broadcast();
        SwapLimiterHook swapLimiter = new SwapLimiterHook{salt: salt}(manager);
        require(address(swapLimiter) == hookAddress, "SwapLimiterScript: hook address mismatch");

        vm.startBroadcast();
        (PoolModifyLiquidityTest lpRouter, PoolSwapTest swapRouter,) = deployRouters(manager);
        vm.stopBroadcast();

        vm.startBroadcast();
        testLifecycle(manager, address(swapLimiter), lpRouter, swapRouter);
        vm.stopBroadcast();
    }

    function deployPoolManager() internal returns (IPoolManager) {
        return IPoolManager(address(new PoolManager()));
    }

    function deployRouters(IPoolManager manager)
        internal
        returns (PoolModifyLiquidityTest lpRouter, PoolSwapTest swapRouter, PoolDonateTest donateRouter)
    {
        lpRouter = new PoolModifyLiquidityTest(manager);
        swapRouter = new PoolSwapTest(manager);
        donateRouter = new PoolDonateTest(manager);
    }

    function deployTokens() internal returns (MockERC20 token0, MockERC20 token1) {
        MockERC20 tokenA = new MockERC20("MockA", "A", 18);
        MockERC20 tokenB = new MockERC20("MockB", "B", 18);
        if (uint160(address(tokenA)) < uint160(address(tokenB))) {
            token0 = tokenA;
            token1 = tokenB;
        } else {
            token0 = tokenB;
            token1 = tokenA;
        }
    }

    function testLifecycle(
        IPoolManager manager,
        address hook,
        PoolModifyLiquidityTest lpRouter,
        PoolSwapTest swapRouter
    ) internal {
        (MockERC20 token0, MockERC20 token1) = deployTokens();
        token0.mint(msg.sender, 100_000 ether);
        token1.mint(msg.sender, 100_000 ether);

        bytes memory ZERO_BYTES = new bytes(0);

        int24 tickSpacing = 60;
        PoolKey memory poolKey = PoolKey(
            Currency.wrap(address(token0)),
            Currency.wrap(address(token1)),
            3000,
            tickSpacing,
            IHooks(hook)
        );
        manager.initialize(poolKey, Constants.SQRT_PRICE_1_1, ZERO_BYTES);

        token0.approve(address(lpRouter), type(uint256).max);
        token1.approve(address(lpRouter), type(uint256).max);
        token0.approve(address(swapRouter), type(uint256).max);
        token1.approve(address(swapRouter), type(uint256).max);

        lpRouter.modifyLiquidity(
            poolKey,
            IPoolManager.ModifyLiquidityParams(
                TickMath.minUsableTick(tickSpacing),
                TickMath.maxUsableTick(tickSpacing),
                100 ether,
                0
            ),
            ZERO_BYTES
        );

        console.log("Starting swap tests...");

        for (uint256 i = 0; i < 6; i++) {
            console.log("Attempting swap %d", i + 1);
            try swapRouter.swap(
                poolKey,
                IPoolManager.SwapParams({
                    zeroForOne: true,
                    amountSpecified: 1 ether,
                    sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
                }),
                PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),
                new bytes(0)
            ) {
                console.log("Swap %d successful", i + 1);
            } catch Error(string memory reason) {
                console.log("Swap %d failed: %s", i + 1, reason);
            } catch (bytes memory /*lowLevelData*/) {
                console.log("Swap %d failed", i + 1);
            }
        }

        console.log("Swap tests completed.");

        SwapLimiterHook swapLimiter = SwapLimiterHook(hook);
        uint256 remainingSwaps = swapLimiter.getRemainingSwaps(address(swapRouter));
        console.log("Remaining swaps for the sender: %d", remainingSwaps);
    }
}

然后,在另一个新版终端窗口中运行以下命令:

forge script script/Anvil.s.sol \
    --rpc-url http://localhost:8545 \
    --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
    --broadcast

上述命令在端口为 8545 的本地测试网节点上执行 Anvil.s.sol 脚本。私钥对应于运行本地测试网代码时生成的账户。

你会看到像这样的响应:

Anvil 响应

注意日志语句,它们显示了每个交换后的状态,直到交换因达到交换限制而回退。

总结

恭喜!你现在已经学习了如何为 Uniswap V4 创建、测试和部署自定义 hook。从理解 hooks 的基础知识到实现自定义 hook,并将其部署到 Sepolia 测试网,你已经涵盖了 Uniswap V4 hook 开发的基本步骤。

通过在 Twitter (@QuickNode) 上关注 QuickNode 或加入 Discord 社区,保持对区块链开发最新动态的了解。

我们 ❤️ 反馈!

告诉我们 如果你有任何反馈或对新主题的请求。我们很乐意听到你的想法。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。