uniswap v4 合约解析1 pool初始化

当我们创建一个pool时,其入口函数位PoolManager合约的initialize方法:

 代码如下:

    /// @inheritdoc IPoolManager
    function initialize(PoolKey memory key, uint160 sqrtPriceX96) external noDelegateCall returns (int24 tick) {
        // see TickBitmap.sol for overflow conditions that can arise from tick spacing being too large
        if (key.tickSpacing > MAX_TICK_SPACING) TickSpacingTooLarge.selector.revertWith(key.tickSpacing);
        if (key.tickSpacing < MIN_TICK_SPACING) TickSpacingTooSmall.selector.revertWith(key.tickSpacing);
        if (key.currency0 >= key.currency1) {
            CurrenciesOutOfOrderOrEqual.selector.revertWith(
                Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)
            );
        }
        if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks));

        uint24 lpFee = key.fee.getInitialLPFee();

        key.hooks.beforeInitialize(key, sqrtPriceX96);

        PoolId id = key.toId();

        tick = _pools[id].initialize(sqrtPriceX96, lpFee);

        // event is emitted before the afterInitialize call to ensure events are always emitted in order
        // emit all details of a pool key. poolkeys are not saved in storage and must always be provided by the caller
        // the key's fee may be a static fee or a sentinel to denote a dynamic fee.
        emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks, sqrtPriceX96, tick);

        key.hooks.afterInitialize(key, sqrtPriceX96, tick);
    }

校验

第一步是校验

if (key.tickSpacing > MAX_TICK_SPACING) TickSpacingTooLarge.selector.revertWith(key.tickSpacing);
if (key.tickSpacing < MIN_TICK_SPACING) TickSpacingTooSmall.selector.revertWith(key.tickSpacing);

根据MAX_TICK_SPACING和MIN_TICK_SPACING的定义,tickSpacing的范围是[1, 32767]

此提供了足够的灵活性,允许开发者根据需求选择适合的tickSpacing

  • 较小的 tickSpacing刻度间距更密集,允许更精细的价格范围。适合高精度的交易场景,但会增加存储和计算成本。

  • 较大的 tickSpacing刻度间距更稀疏,减少存储和计算成本。适合低精度的交易场景,但可能限制流动性提供者的灵活性。

if (key.currency0 >= key.currency1) {
   CurrenciesOutOfOrderOrEqual.selector.revertWith(
        Currency.unwrap(key.currency0), Currency.unwrap(key.currency1)
   );
}

这段代码的作用是对 key.currency0 和 key.currency1 进行验证,确保它们的顺序正确且不相等。如果 key.currency0 大于或等于 key.currency1,则抛出 CurrenciesOutOfOrderOrEqual 错误。

用户在手动调用合约创建交易对时,需要注意poolKey参数里代币地址的大小顺序。

Currency.unwrap(key.currency0)将 Currency 类型的值解包为其底层的原始值address便于输出和调试。

if (!key.hooks.isValidHookAddress(key.fee)) Hooks.HookAddressNotValid.selector.revertWith(address(key.hooks));

 这里主要是对pool中包含的钩子地址进行校验和交易费用进行校验,代码如下:

    /// @notice Ensures that the hook address includes at least one hook flag or dynamic fees, or is the 0 address
    /// @param self The hook to verify
    /// @param fee The fee of the pool the hook is used with
    /// @return bool True if the hook address is valid
    function isValidHookAddress(IHooks self, uint24 fee) internal pure returns (bool) {
        // The hook can only have a flag to return a hook delta on an action if it also has the corresponding action flag
        if (!self.hasPermission(BEFORE_SWAP_FLAG) && self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) return false;
        if (!self.hasPermission(AFTER_SWAP_FLAG) && self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)) return false;
        if (!self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG) && self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG))
        {
            return false;
        }
        if (
            !self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)
                && self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)
        ) return false;

        // If there is no hook contract set, then fee cannot be dynamic
        // If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee
        return address(self) == address(0)
            ? !fee.isDynamicFee()
            : (uint160(address(self)) & ALL_HOOK_MASK > 0 || fee.isDynamicFee());
    }

代码比较简单,但是需要先了解一下hooks地址的标志位

这里需要介绍一下动态费用,我们知道uniswap v3支持的交易费用为0.01%0.05%0.3% 和 1%,而v4在此基础上则增加了动态费用,支持根据交易情况在钩子里动态调整交易费用,也就是说当我们把交易费用设置为0x800000(0x代表16进制),转换成2进制为0b100000000000000000000000,则代表当前的交易费需要通过钩子调整,所以如用户设置了动态费用但是没有设置任何hooks,则校验失败。

uint24 lpFee = key.fee.getInitialLPFee();

这里是获取用户的设置的交易费,如果是动态费用则返回0,同时这里也包含了一步校验,如果费用超过100%,会抛出异常。

   /// @notice An lp fee of exactly 0b1000000... signals a dynamic fee pool. This isn't a valid static fee as it is > MAX_LP_FEE
    uint24 public constant DYNAMIC_FEE_FLAG = 0x800000;

    /// @notice the second bit of the fee returned by beforeSwap is used to signal if the stored LP fee should be overridden in this swap
    // only dynamic-fee pools can return a fee via the beforeSwap hook
    uint24 public constant OVERRIDE_FEE_FLAG = 0x400000;

    /// @notice mask to remove the override fee flag from a fee returned by the beforeSwaphook
    uint24 public constant REMOVE_OVERRIDE_MASK = 0xBFFFFF;

    /// @notice the lp fee is represented in hundredths of a bip, so the max is 100%
    uint24 public constant MAX_LP_FEE = 1000000;    

function isDynamicFee(uint24 self) internal pure returns (bool) {
        return self == DYNAMIC_FEE_FLAG;
    }

    /// @notice returns true if an LP fee is valid, aka not above the maximum permitted fee
    /// @param self The fee to check
    /// @return bool True of the fee is valid
    function isValid(uint24 self) internal pure returns (bool) {
        return self <= MAX_LP_FEE;
    }

    /// @notice validates whether an LP fee is larger than the maximum, and reverts if invalid
    /// @param self The fee to validate
    function validate(uint24 self) internal pure {
        if (!self.isValid()) LPFeeTooLarge.selector.revertWith(self);
    }

    /// @notice gets and validates the initial LP fee for a pool. Dynamic fee pools have an initial fee of 0.
    /// @dev if a dynamic fee pool wants a non-0 initial fee, it should call `updateDynamicLPFee` in the afterInitialize hook
    /// @param self The fee to get the initial LP from
    /// @return initialFee 0 if the fee is dynamic, otherwise the fee (if valid)
    function getInitialLPFee(uint24 self) internal pure returns (uint24) {
        // the initial fee for a dynamic fee pool is 0
        if (self.isDynamicFee()) return 0;
        self.validate();
        return self;
    }

初始化

接下来就是正式创建交易对

key.hooks.beforeInitialize(key, sqrtPriceX96);

PoolId id = key.toId();

tick = _pools[id].initialize(sqrtPriceX96, lpFee);

// event is emitted before the afterInitialize call to ensure events are always emitted in order
// emit all details of a pool key. poolkeys are not saved in storage and must always be provided by the caller
// the key's fee may be a static fee or a sentinel to denote a dynamic fee.
emit Initialize(id, key.currency0, key.currency1, key.fee, key.tickSpacing, key.hooks, sqrtPriceX96, tick);

key.hooks.afterInitialize(key, sqrtPriceX96, tick);

创建交易对前后需要执行相应的钩子beforeInitialize和afterInitialize

tick = _pools[id].initialize(sqrtPriceX96, lpFee);这部分是真正初始化交易对。

_pools是一个映射(mapping),在 Solidity 中,映射的默认行为是:如果访问一个未初始化的键(id),它会返回该键对应值类型的默认值

根据_pools的定义

mapping(PoolId id => Pool.State) internal _pools;

如果 id 对应的池(Pool.State)从未被初始化,_pools[id] 会返回一个默认值(即 Pool.State 的默认状态)。

    struct State {

        Slot0 slot0;

        uint256 feeGrowthGlobal0X128;

        uint256 feeGrowthGlobal1X128;

        uint128 liquidity;

        mapping(int24 tick => TickInfo) ticks;

        mapping(int16 wordPos => uint256) tickBitmap;

        mapping(bytes32 positionKey => Position.State) positions;

    }

相应的基本类型和结构体(slot0)中的基本类型都会初始化为0

pool库通过using Pool for State将 Pool 库中的函数与 State 结构体相关联,所以_pools[id].initialize(sqrtPriceX96, lpFee);会直接调用poolinitialize方法。

    function initialize(State storage self, uint160 sqrtPriceX96, uint24 lpFee) internal returns (int24 tick) {
        if (self.slot0.sqrtPriceX96() != 0) PoolAlreadyInitialized.selector.revertWith();

        tick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);

        // the initial protocolFee is 0 so doesn't need to be set
        self.slot0 = Slot0.wrap(bytes32(0)).setSqrtPriceX96(sqrtPriceX96).setTick(tick).setLpFee(lpFee);
    }
  • 第一行是校验
  • 第二行是通过初始的价格获取相应的tick,
  • 第三行设置当前pool的价格和tick。

第二行代码的实现是最复杂的,详见:uniswap getTickAtSqrtPrice 方法解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值