Base

基础合约实现在库中作为构建块提供,以利用 Uniswap v4 的原生特性,例如自定义记账、自定义曲线和异步交换。

Hook

BaseHook 作为底层的脚手架合约提供。它声明了每个支持的 hook 回调,以及强制执行安全性和防止滥用的修饰符和 revert 语句。在设计上,所有 hook 入口点/动作都被关闭。这允许继承合约通过覆盖 getHookPermissions 中的 permissions 结构并实现各自的内部函数来选择启用哪些方法。

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

import {BaseHook} from "src/base/BaseHook.sol";
import {Hooks} from "v4-core/src/libraries/Hooks.sol";
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol";

contract CounterHook is BaseHook {
    uint256 public counter;

    constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}

    /**
     * @inheritdoc BaseHook
     */
    function _beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
        internal
        virtual
        override
        returns (bytes4, BeforeSwapDelta, uint24)
    {
        counter++;
        return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0);
    }

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

BaseHook 强制执行的安全检查中,validateHookAddress 确保合约地址与声明的权限匹配。

Custom Accounting

BaseCustomAccounting 继承自 BaseHook,以强制执行 hook 拥有的流动性,并允许为特定池进行自定义 token 记账。流动性修改(添加/移除)由 hook 合约直接处理,然后通过 PoolManager 将其应用于池。

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

import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
import {BaseCustomAccounting} from "src/base/BaseCustomAccounting.sol";
import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {SafeCast} from "v4-core/src/libraries/SafeCast.sol";
import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol";

contract SimpleAccounting is BaseCustomAccounting, ERC20 {
    using SafeCast for uint256;
    using StateLibrary for IPoolManager;

    constructor(IPoolManager _poolManager) BaseCustomAccounting(_poolManager) ERC20("Mock", "MOCK") {}

    /// @inheritdoc BaseCustomAccounting
    function _getAddLiquidity(uint160 sqrtPriceX96, AddLiquidityParams memory params)
        internal
        pure
        override
        returns (bytes memory modify, uint256 liquidity)
    {
        liquidity = LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            TickMath.getSqrtPriceAtTick(params.tickLower),
            TickMath.getSqrtPriceAtTick(params.tickUpper),
            params.amount0Desired,
            params.amount1Desired
        );

        return (
            abi.encode(
                IPoolManager.ModifyLiquidityParams({
                    tickLower: params.tickLower,
                    tickUpper: params.tickUpper,
                    liquidityDelta: liquidity.toInt256(),
                    salt: 0
                })
            ),
            liquidity
        );
    }

    /// @inheritdoc BaseCustomAccounting
    function _getRemoveLiquidity(RemoveLiquidityParams memory params)
        internal
        view
        override
        returns (bytes memory, uint256 liquidity)
    {
        liquidity = FullMath.mulDiv(params.liquidity, poolManager.getLiquidity(poolKey.toId()), totalSupply());

        return (
            abi.encode(
                IPoolManager.ModifyLiquidityParams({
                    tickLower: params.tickLower,
                    tickUpper: params.tickUpper,
                    liquidityDelta: -liquidity.toInt256(),
                    salt: 0
                })
            ),
            liquidity
        );
    }

    /// @inheritdoc BaseCustomAccounting
    function _mint(AddLiquidityParams memory params, BalanceDelta, uint256 liquidity) internal override {
        _mint(params.to, liquidity);
    }

    /// @inheritdoc BaseCustomAccounting
    function _burn(RemoveLiquidityParams memory, BalanceDelta, uint256 liquidity) internal override {
        _burn(msg.sender, liquidity);
    }
}

继承合约必须实现各自的函数来计算流动性修改参数和要铸造或销毁的流动性份额数量。此外,实现者必须记住,hook 是唯一的流动性所有者,因此负责管理任何流动性份额上的费用。

Custom Curve

在自定义记账的基础上,BaseCustomCurve 通过允许开发者完全替换 Uniswap v4 的默认集中流动性数学与他们自己的交换逻辑,使自定义更进一步。

通过覆盖 _beforeSwap 函数,继承合约可以实现自己的交换逻辑和曲线。因为 hook 仍然拥有流动性,所以它可以以不同于标准不变式的方式路由 token,也许采用稳定币交换曲线、绑定曲线或其他更适合专门用途的设计。该合约还重新定义了流动性添加和移除如何在内部发生,但它以一种与 Uniswap v4 引擎的其余架构和路由器保持兼容的方式进行。

Async Swap

BaseAsyncSwap 提供了一种跳过 PoolManager 执行精确输入交换的方法,以支持异步交换和其他需要非原子执行的情况。

当处理精确输入交换时,hook 返回一个将输入量净额设置为零的 delta,然后将 ERC-6909 token 铸造到合约地址。这种方法有效地绕过了标准交换逻辑,并允许 hook 管理用户头寸或 token,直到最终结算阶段。用户的输入 token 由 hook 合约持有,该合约可以稍后根据实现者定义的逻辑进行兑换或结算。