死磕Uniswap V4(五):费用系统与动态费率

本文是「死磕Uniswap V4」系列的第五篇,深入剖析V4的费用系统架构和动态费率实现。

系列导航

序号标题核心内容
01V4概述与架构革命Singleton、Hooks、Flash Accounting
02Hooks机制深度解析Hook接口、生命周期、实现模式
03单例架构与瞬时会计PoolManager、Currency、Accounting
04交换流程与Hook执行时序swap函数、Hook调用链、Gas分析
05费用系统与动态费率自定义费率、动态调整、费用分配
06账户抽象与原生ETHCurrency类型、settle/take、批量操作
07安全分析与最佳实践Hook安全、MEV防护、审计要点

1. V4费用系统架构

1.1 从V3到V4的费用变化

graph TB
    subgraph V3Fees["V3费用系统"]
        V3T[固定费率等级]
        V3F1[0.01%]
        V3F2[0.05%]
        V3F3[0.3%]
        V3F4[1%]

        V3T --> V3F1
        V3T --> V3F2
        V3T --> V3F3
        V3T --> V3F4
    end

    subgraph V4Fees["V4费用系统"]
        V4T[自定义费率]
        V4Base[基础费率]
        V4Hook[Hook动态费率]
        V4Dyn[实时调整]

        V4T --> V4Base
        V4T --> V4Hook
        V4Hook --> V4Dyn
    end

    style V4T fill:#e3f2fd
    style V4Dyn fill:#c8e6c9

1.2 费用结构定义

/// @notice 费用配置
library Pool {
    struct Fees {
        uint16 fee;     // 基础费率(单位:1e-6,即0.0001%)
        uint16 hookFee; // Hook控制的动态费率部分
    }
}
 
/// @notice 协议费用
struct ProtocolFees {
    uint16 token0; // token0的协议费率(单位:1e-4,即0.01%)
    uint16 token1; // token1的协议费率(单位:1e-4,即0.01%)
}

费率单位说明:

费用类型单位示例值实际费率
fee1e-630000.3%
hookFee1e-65000.05%
协议费1e-4100.1%

1.3 总费率计算

/// @notice 计算实际总费率
/// @return 总费率(单位:1e-6)
function getTotalFee(
    bytes32 poolId,
    Pool.Fees memory fees,
    uint256 protocolFee
) internal pure returns (uint256) {
    // 总费率 = 基础费率 + Hook费率 + 协议费率
    uint256 totalFee = uint256(fees.fee) + uint256(fees.hookFee);
 
    // 协议费从LP费用中扣除
    // 所以LP实际收到的费用 = totalFee - protocolFee
 
    return totalFee;
}
 
/// @notice 计算LP实际收到的费用
/// @return LP费用率(单位:1e-6)
function getLpFee(
    uint256 totalFee,
    uint256 protocolFee
) internal pure returns (uint256) {
    return totalFee - (protocolFee * 10000 / 10000);  // 协议费率单位转换
}

2. 动态费率实现

2.1 动态费率设计理念

传统AMM的固定费率无法适应不同市场条件:

graph LR
    subgraph StaticFee["固定费率问题"]
        A1[低波动期<br/>费率过高] --> A2[交易量低]
        A3[高波动期<br/>费率过低] --> A4[LP收益不足]
    end

    subgraph DynamicFee["动态费率优势"]
        B1[低波动期<br/>降低费率] --> B2[吸引交易量]
        B3[高波动期<br/>提高费率] --> B4[补偿LP风险]
    end

    style DynamicFee fill:#c8e6c9

2.2 基于波动率的动态费率

/// @notice 基于波动率的动态费率Hook
contract VolatilityBasedFeeHook is IHooks {
    IPoolManager public immutable poolManager;
 
    /// @notice 费率参数
    struct FeeParams {
        uint256 baseFee;           // 基础费率
        uint256 minFee;            // 最低费率
        uint256 maxFee;            // 最高费率
        uint256 volatilityWindow;  // 波动率计算窗口
        uint256 volatilityMultiplier; // 波动率乘数
    }
 
    mapping(bytes32 => FeeParams) public feeParams;
 
    /// @notice 波动率追踪
    struct VolatilityTracker {
        uint256 lastTimestamp;
        uint256 lastPrice;
        uint256[] priceHistory;
    }
 
    mapping(bytes32 => VolatilityTracker) public volatilityTrackers;
 
    constructor(IPoolManager _poolManager) {
        poolManager = _poolManager;
    }
 
    /// @notice 设置费率参数
    function setFeeParams(
        bytes32 poolId,
        uint256 baseFee,
        uint256 minFee,
        uint256 maxFee,
        uint256 volatilityWindow,
        uint256 volatilityMultiplier
    ) external {
        feeParams[poolId] = FeeParams({
            baseFee: baseFee,
            minFee: minFee,
            maxFee: maxFee,
            volatilityWindow: volatilityWindow,
            volatilityMultiplier: volatilityMultiplier
        });
    }
 
    /// @notice beforeSwap Hook:动态调整费率
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, int256, int256) {
        bytes32 poolId = key.poolId;
 
        // 获取当前价格
        (uint160 sqrtPriceX96, , , ) = poolManager.slot0s(poolId);
        uint256 currentPrice = uint256(sqrtPriceX96);
 
        // 更新波动率
        uint256 volatility = _updateVolatility(poolId, currentPrice);
 
        // 计算动态费率
        FeeParams memory params = feeParams[poolId];
        uint256 dynamicFee = _calculateDynamicFee(
            volatility,
            params.baseFee,
            params.minFee,
            params.maxFee,
            params.volatilityMultiplier
        );
 
        // 更新Hook费率
        uint16 hookFee = uint16(dynamicFee - params.baseFee);
        poolManager.setHookFee(poolId, hookFee);
 
        return (IHooks.beforeSwap.selector, 0, 0);
    }
 
    /// @notice 更新波动率
    function _updateVolatility(
        bytes32 poolId,
        uint256 currentPrice
    ) private returns (uint256) {
        VolatilityTracker storage tracker = volatilityTrackers[poolId];
 
        if (tracker.lastTimestamp == 0) {
            // 首次调用
            tracker.lastPrice = currentPrice;
            tracker.lastTimestamp = block.timestamp;
            tracker.priceHistory.push(currentPrice);
            return 0;
        }
 
        // 添加当前价格到历史
        tracker.priceHistory.push(currentPrice);
 
        // 移除超出窗口的价格
        FeeParams memory params = feeParams[poolId];
        uint256 windowSeconds = params.volatilityWindow;
 
        while (
            tracker.priceHistory.length > 1 &&
            tracker.lastTimestamp - (block.timestamp - windowSeconds * tracker.priceHistory.length) > windowSeconds
        ) {
            tracker.priceHistory[0] = tracker.priceHistory[tracker.priceHistory.length - 1];
            tracker.priceHistory.pop();
        }
 
        // 计算波动率(标准差)
        uint256 volatility = _calculateStandardDeviation(tracker.priceHistory);
 
        tracker.lastPrice = currentPrice;
        tracker.lastTimestamp = block.timestamp;
 
        return volatility;
    }
 
    /// @notice 计算标准差
    function _calculateStandardDeviation(uint256[] memory prices) private pure returns (uint256) {
        if (prices.length < 2) return 0;
 
        // 计算平均价格
        uint256 sum = 0;
        for (uint256 i = 0; i < prices.length; i++) {
            sum += prices[i];
        }
        uint256 mean = sum / prices.length;
 
        // 计算方差
        uint256 variance = 0;
        for (uint256 i = 0; i < prices.length; i++) {
            uint256 diff = prices[i] > mean ? prices[i] - mean : mean - prices[i];
            variance += (diff * diff) / prices.length;
        }
 
        // 返回标准差(平方根的近似值)
        return sqrt(variance);
    }
 
    /// @notice 计算动态费率
    function _calculateDynamicFee(
        uint256 volatility,
        uint256 baseFee,
        uint256 minFee,
        uint256 maxFee,
        uint256 multiplier
    ) private pure returns (uint256) {
        // 费率调整 = 基础费率 + 波动率 * 乘数
        uint256 feeAdjustment = (volatility * multiplier) / 1e18;
        uint256 dynamicFee = baseFee + feeAdjustment;
 
        // 限制在范围内
        if (dynamicFee < minFee) dynamicFee = minFee;
        if (dynamicFee > maxFee) dynamicFee = maxFee;
 
        return dynamicFee;
    }
 
    /// @notice 平方根近似计算
    function sqrt(uint256 x) private pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = (x + 1) / 2;
        uint256 y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
        return y;
    }
}

2.3 基于交易量的动态费率

/// @notice 基于交易量的阶梯费率Hook
contract VolumeBasedFeeHook is IHooks {
    IPoolManager public immutable poolManager;
 
    /// @notice 交易量区间和对应费率
    struct VolumeTier {
        uint256 minVolume;
        uint256 maxVolume;
        uint256 fee;
    }
 
    mapping(bytes32 => VolumeTier[]) public volumeTiers;
 
    /// @notice 累计交易量
    mapping(bytes32 => uint256) public cumulativeVolume;
    mapping(bytes32 => uint256) public lastResetTime;
 
    /// @notice 设置阶梯费率
    function setVolumeTiers(
        bytes32 poolId,
        VolumeTier[] calldata tiers
    ) external {
        delete volumeTiers[poolId];
        for (uint256 i = 0; i < tiers.length; i++) {
            volumeTiers[poolId].push(tiers[i]);
        }
    }
 
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, int256, int256) {
        bytes32 poolId = key.poolId;
        uint256 amount = abs(params.amountSpecified);
 
        // 更新累计交易量
        cumulativeVolume[poolId] += amount;
 
        // 获取当前时间窗口的交易量
        uint256 windowVolume = _getWindowVolume(poolId);
 
        // 查找对应的费率档位
        uint256 dynamicFee = _getFeeByVolume(poolId, windowVolume);
 
        // 更新Hook费率
        uint16 hookFee = uint16(dynamicFee - 3000); // 假设基础费率为3000
        poolManager.setHookFee(poolId, hookFee);
 
        return (IHooks.beforeSwap.selector, 0, 0);
    }
 
    function _getWindowVolume(bytes32 poolId) private view returns (uint256) {
        // 获取最近1小时的交易量
        uint256 resetTime = lastResetTime[poolId];
        if (block.timestamp - resetTime > 1 hours) {
            return 0;
        }
        return cumulativeVolume[poolId];
    }
 
    function _getFeeByVolume(
        bytes32 poolId,
        uint256 volume
    ) private view returns (uint256) {
        VolumeTier[] storage tiers = volumeTiers[poolId];
 
        for (uint256 i = 0; i < tiers.length; i++) {
            if (volume >= tiers[i].minVolume && volume < tiers[i].maxVolume) {
                return tiers[i].fee;
            }
        }
 
        // 默认费率
        return 3000;
    }
 
    function abs(int256 x) private pure returns (uint256) {
        return x >= 0 ? uint256(x) : uint256(-x);
    }
}

2.4 基于时间的动态费率

/// @notice 基于时间的动态费率Hook
contract TimeBasedFeeHook is IHooks {
    IPoolManager public immutable poolManager;
 
    /// @notice 时间段费率配置
    struct TimeFee {
        uint256 startHour;  // 开始小时(0-23)
        uint256 endHour;    // 结束小时(0-23)
        uint256 fee;        // 费率
    }
 
    TimeFee[] public timeFees;
 
    constructor(IPoolManager _poolManager, TimeFee[] memory _timeFees) {
        poolManager = _poolManager;
        for (uint256 i = 0; i < _timeFees.length; i++) {
            timeFees.push(_timeFees[i]);
        }
    }
 
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, int256, int256) {
        // 获取当前小时(UTC)
        uint256 currentHour = (block.timestamp / 3600) % 24;
 
        // 查找对应的费率
        uint256 dynamicFee = _getFeeByTime(currentHour);
 
        // 更新Hook费率
        uint16 hookFee = uint16(dynamicFee - 3000);
        poolManager.setHookFee(key.poolId, hookFee);
 
        return (IHooks.beforeSwap.selector, 0, 0);
    }
 
    function _getFeeByTime(uint256 hour) private view returns (uint256) {
        for (uint256 i = 0; i < timeFees.length; i++) {
            uint256 start = timeFees[i].startHour;
            uint256 end = timeFees[i].endHour;
 
            if (end > start) {
                // 正常时间段
                if (hour >= start && hour < end) {
                    return timeFees[i].fee;
                }
            } else {
                // 跨天时间段
                if (hour >= start || hour < end) {
                    return timeFees[i].fee;
                }
            }
        }
 
        // 默认费率
        return 3000;
    }
}

3. 费用增长追踪

3.1 feeGrowthGlobal结构

/// @notice 费用增长追踪
contract PoolManager {
    /// @notice 每个流动性单位的费用累积
    mapping(bytes32 poolId => uint256) public feeGrowthGlobal0X128;  // token0
    mapping(bytes32 poolId => uint256) public feeGrowthGlobal1X128;  // token1
 
    /// @notice 更新费用增长
    function _updateFeeGrowth(
        bytes32 poolId,
        uint256 feeAmount0,
        uint256 feeAmount1,
        uint128 liquidity
    ) internal {
        if (liquidity > 0) {
            // 费用增长 = 费用金额 / 流动性 * Q128
            feeGrowthGlobal0X128[poolId] += FullMath.mulDiv(
                feeAmount0,
                FixedPoint128.Q128,
                liquidity
            );
 
            feeGrowthGlobal1X128[poolId] += FullMath.mulDiv(
                feeAmount1,
                FixedPoint128.Q128,
                liquidity
            );
        }
    }
}

3.2 费用增长计算图

flowchart LR
    A[交换执行] --> B[计算费用<br/>feeAmount]
    B --> C[获取当前流动性<br/>liquidity]
    C --> D[费用增长<br/>feeGrowth = fee * Q128 / liquidity]
    D --> E[累加到<br/>feeGrowthGlobal]

    E --> F[LP领取费用时<br/>计算应得份额]
    F --> G[份额 = 当前feeGrowth<br/>- 上次feeGrowth]

3.3 Tick级别的费用追踪

/// @notice Tick信息
library Tick {
    struct Info {
        uint128 liquidityGross;              // 总流动性
        int128 liquidityNet;                 // 净流动性
        uint256 feeGrowthOutside0X128;       // 外部费用增长(token0)
        uint256 feeGrowthOutside1X128;       // 外部费用增长(token1)
        int56 tickCumulativeOutside;        // 外部tick累积
        uint160 secondsPerLiquidityOutsideX128; // 外部每流动性秒数
        uint32 secondsOutside;               // 外部秒数
        bool initialized;                    // 是否已初始化
    }
}
 
/// @notice 更新Tick的外部费用
function _updateTickFees(
    bytes32 poolId,
    int24 tick,
    bool zeroForOne,
    uint256 feeGrowthGlobal0,
    uint256 feeGrowthGlobal1
) internal {
    Tick.Info storage tickInfo = ticks[poolId][tick];
 
    if (zeroForOne) {
        // 向左移动,当前tick的外部 = 当前全局
        tickInfo.feeGrowthOutside0X128 = feeGrowthGlobal0;
        tickInfo.feeGrowthOutside1X128 = feeGrowthGlobal1;
    } else {
        // 向右移动,交换处理
        tickInfo.feeGrowthOutside0X128 = feeGrowthGlobal0;
        tickInfo.feeGrowthOutside1X128 = feeGrowthGlobal1;
    }
}

4. Hook费用分配

4.1 费用分配流程

sequenceDiagram
    participant U as User
    participant PM as PoolManager
    participant H as Hook
    participant LP as LP提供者

    U->>PM: swap()
    PM->>PM: 计算费用

    PM->>H: afterSwap() Hook
    H->>H: 计算Hook费用份额

    alt Hook需要费用
        H->>PM: 更新deltas收取费用
    end

    PM->>PM: 计算LP费用份额
    PM->>PM: 更新feeGrowthGlobal

    PM-->>U: 返回结果

    Note over LP: LP领取费用时
    LP->>PM: collect()
    PM->>LP: 转账累积费用

4.2 Hook费用收取

/// @notice 在beforeSwap中收取额外费用
function beforeSwap(
    address sender,
    PoolKey calldata key,
    IPoolManager.SwapParams calldata params,
    bytes calldata hookData
) external returns (bytes4, int256, int256) {
    bytes32 poolId = key.poolId;
 
    // 计算Hook费用(例如:基于交易量的百分比)
    uint256 hookFeeAmount = abs(params.amountSpecified) / 1000; // 0.1%
 
    int256 delta0 = 0;
    int256 delta1 = 0;
 
    if (params.zeroForOne) {
        // 卖出token0,收取token0作为Hook费用
        delta0 = int256(hookFeeAmount);
    } else {
        // 卖出token1,收取token1作为Hook费用
        delta1 = int256(hookFeeAmount);
    }
 
    // 记录费用收入
    _recordHookFee(poolId, params.zeroForOne ? key.currency0 : key.currency1, hookFeeAmount);
 
    return (IHooks.beforeSwap.selector, delta0, delta1);
}
 
/// @notice 记录Hook费用
mapping(bytes32 => mapping(Currency => uint256)) public hookFees;
 
function _recordHookFee(
    bytes32 poolId,
    Currency currency,
    uint256 amount
) private {
    hookFees[poolId][currency] += amount;
    emit HookFeeCollected(poolId, currency, amount);
}
 
/// @notice 提取Hook费用
function withdrawHookFees(
    bytes32 poolId,
    Currency currency,
    address recipient,
    uint256 amount
) external {
    require(hookFees[poolId][currency] >= amount, "Insufficient fees");
    hookFees[poolId][currency] -= amount;
 
    if (CurrencyLibrary.isNative(currency)) {
        payable(recipient).transfer(amount);
    } else {
        ERC20(Currency.unwrap(currency)).transfer(recipient, amount);
    }
 
    emit HookFeeWithdrawn(poolId, currency, recipient, amount);
}

4.3 LP费用分配

/// @notice LP领取费用
function collect(
    PoolKey calldata key,
    int24 tickLower,
    int24 tickUpper,
    uint128 amount0,
    uint128 amount1
) external returns (uint256 collected0, uint256 collected1) {
    bytes32 poolId = key.poolId;
 
    // 获取当前的费用增长
    uint256 feeGrowthGlobal0 = feeGrowthGlobal0X128[poolId];
    uint256 feeGrowthGlobal1 = feeGrowthGlobal1X128[poolId];
 
    // 获取用户上次领取的费用增长
    (uint256 userFeeGrowth0, uint256 userFeeGrowth1) = _getUserFeeGrowth(
        msg.sender,
        poolId,
        tickLower,
        tickUpper
    );
 
    // 计算新产生的费用
    uint256 feeGrowth0 = feeGrowthGlobal0 - userFeeGrowth0;
    uint256 feeGrowth1 = feeGrowthGlobal1 - userFeeGrowth1;
 
    // 获取用户的流动性
    uint128 liquidity = _getUserLiquidity(msg.sender, poolId, tickLower, tickUpper);
 
    // 计算可领取的费用
    uint256 collectable0 = FullMath.mulDiv(liquidity, feeGrowth0, FixedPoint128.Q128);
    uint256 collectable1 = FullMath.mulDiv(liquidity, feeGrowth1, FixedPoint128.Q128);
 
    // 限制领取金额
    if (amount0 > 0 && collectable0 > amount0) collectable0 = amount0;
    if (amount1 > 0 && collectable1 > amount1) collectable1 = amount1;
 
    // 更新用户的费用增长记录
    _setUserFeeGrowth(
        msg.sender,
        poolId,
        tickLower,
        tickUpper,
        feeGrowthGlobal0,
        feeGrowthGlobal1
    );
 
    // 转账费用
    if (collectable0 > 0) {
        deltas[msg.sender][key.currency0] += int256(collectable0);
    }
    if (collectable1 > 0) {
        deltas[msg.sender][key.currency1] += int256(collectable1);
    }
 
    emit Collect(msg.sender, poolId, tickLower, tickUpper, collectable0, collectable1);
 
    return (collectable0, collectable1);
}

5. 协议费用

5.1 协议费用结构

/// @notice 协议费用配置
struct ProtocolFees {
    uint16 token0; // token0协议费率(单位:1e-4,即0.01%)
    uint16 token1; // token1协议费率(单位:1e-4,即0.01%)
}
 
/// @notice 协议费用累积
uint256 public protocolFees0; // 累积的token0协议费
uint256 public protocolFees1; // 累积的token1协议费

5.2 协议费用计算

/// @notice 计算协议费用
function _calculateProtocolFees(
    bytes32 poolId,
    uint256 feeAmount0,
    uint256 feeAmount1
) internal view returns (uint256 protocolFee0, uint256 protocolFee1) {
    ProtocolFees memory protocolFees = protocolFees[poolId];
 
    // 协议费 = 总费用 * 协议费率 / 10000
    if (protocolFees.token0 > 0) {
        protocolFee0 = feeAmount0 * protocolFees.token0 / 10000;
    }
    if (protocolFees.token1 > 0) {
        protocolFee1 = feeAmount1 * protocolFees.token1 / 10000;
    }
}
 
/// @notice 提取协议费用
function collectProtocolFees(
    Currency currency,
    address recipient,
    uint256 amount
) external {
    require(msg.sender == owner, "Not owner");
 
    if (CurrencyLibrary.isNative(currency)) {
        require(protocolFees0 >= amount, "Insufficient fees");
        protocolFees0 -= amount;
        payable(recipient).transfer(amount);
    } else {
        uint256 fees = currency == poolKey.currency0 ? protocolFees0 : protocolFees1;
        require(fees >= amount, "Insufficient fees");
        if (currency == poolKey.currency0) {
            protocolFees0 -= amount;
        } else {
            protocolFees1 -= amount;
        }
        ERC20(Currency.unwrap(currency)).transfer(recipient, amount);
    }
 
    emit ProtocolFeesCollected(currency, recipient, amount);
}

6. 费用系统对比

6.1 V3 vs V4 费用对比

graph TB
    subgraph V3FeeSystem["V3费用系统"]
        V3Fixed[固定费率]
        V3Tiers[4个档位]
        V3Update[需要新版本升级]
    end

    subgraph V4FeeSystem["V4费用系统"]
        V4Dynamic[动态费率]
        V4Custom[任意自定义]
        V4Hook[Hook实现]
    end

    V3Fixed --> V3Tiers
    V3Tiers --> V3Update

    V4Dynamic --> V4Custom
    V4Custom --> V4Hook

    style V4Dynamic fill:#c8e6c9

6.2 费用对比表

特性V3V4
费率设置固定4档任意自定义
动态调整✅(Hook)
调整频率从不实时
自定义模型
费用分配手动自动(Hook)
协议费用固定开关动态配置

7. 实际应用案例

7.1 稳定币最优费率

/// @notice 稳定币最优费率Hook
/// @dev 稳定币对价格波动小,可以使用极低费率
contract StablecoinOptimalFeeHook is IHooks {
    IPoolManager public immutable poolManager;
 
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, int256, int256) {
        // 稳定币对使用极低费率(0.01%)
        uint256 stablecoinFee = 100; // 0.01%
 
        // 检查价格偏差
        (uint160 sqrtPriceX96, , , ) = poolManager.slot0s(key.poolId);
        uint256 currentPrice = uint256(sqrtPriceX96);
        uint256 targetPrice = 1 << 96; // 理想价格 = 1
 
        // 价格偏差越大,费率越高
        uint256 priceDeviation = currentPrice > targetPrice
            ? currentPrice - targetPrice
            : targetPrice - currentPrice;
 
        uint256 dynamicFee = stablecoinFee + (priceDeviation / 1e20);
 
        // 限制在0.01%-0.05%范围内
        if (dynamicFee < 100) dynamicFee = 100;
        if (dynamicFee > 500) dynamicFee = 500;
 
        uint16 hookFee = uint16(dynamicFee - 100);
        poolManager.setHookFee(key.poolId, hookFee);
 
        return (IHooks.beforeSwap.selector, 0, 0);
    }
}

7.2 高波动资产保护

/// @notice 高波动资产保护Hook
/// @dev 对高波动资产收取更高费用以补偿LP风险
contract HighVolatilityProtectionHook is IHooks {
    IPoolManager public immutable poolManager;
 
    function beforeSwap(
        address sender,
        PoolKey calldata key,
        IPoolManager.SwapParams calldata params,
        bytes calldata hookData
    ) external returns (bytes4, int256, int256) {
        bytes32 poolId = key.poolId;
 
        // 计算最近24小时的价格变化
        uint256 priceChange = _get24HourPriceChange(poolId);
 
        // 价格变化越大,费率越高
        uint256 baseFee = 3000; // 0.3%
        uint256 volatilityFee = priceChange * 2; // 双倍价格变化作为额外费率
 
        uint256 dynamicFee = baseFee + volatilityFee;
 
        // 限制在1%以内
        if (dynamicFee > 10000) dynamicFee = 10000;
 
        uint16 hookFee = uint16(dynamicFee - baseFee);
        poolManager.setHookFee(poolId, hookFee);
 
        return (IHooks.beforeSwap.selector, 0, 0);
    }
 
    function _get24HourPriceChange(bytes32 poolId) private view returns (uint256) {
        // 实现价格变化计算
        // ...
        return 0;
    }
}

8. 本章小结

8.1 费用系统总结

mindmap
  root((V4费用系统))
    费用结构
      基础费率
      Hook动态费率
      协议费用
    动态费率
      波动率调整
      交易量调整
      时间调整
      自定义模型
    费用追踪
      feeGrowthGlobal
      Tick级别追踪
      用户份额计算
    费用分配
      LP费用
      Hook费用
      协议费用

8.2 关键概念回顾

概念说明单位
fee基础费率1e-6 (0.0001%)
hookFeeHook控制的费率1e-6 (0.0001%)
feeGrowthGlobal全局费用增长Q128
协议费协议抽取的费用1e-4 (0.01%)

8.3 动态费率策略

策略适用场景优点
波动率调整高波动资产补偿LP风险
交易量调整活跃交易对优化流动性
时间调整特定时段需求灵活定价
价格偏差稳定币对套利激励

下一篇预告

在下一篇文章中,我们将深入探讨账户抽象与原生ETH,包括:

  • Currency类型详解
  • settle()和take()函数原理
  • 批量操作实现
  • 元交易支持
  • Gas优化技巧

参考资料