uniswap v4 合约解析2 更新pool流动性

这一章我们分析pool中流动性的修改过程,

在阅读本章之前一定要先通读uniswap v3/v4 中pool的状态管理

在阅读本章之前一定要先通读uniswap v3/v4 中pool的状态管理

在阅读本章之前一定要先通读uniswap v3/v4 中pool的状态管理

代码位置如下:

 这个方法提供增加或减少流动性的功能,并计算用户应得的手续费(feesAccrued)和流动性变化(callerDelta),同时支持钩子(Hooks)机制,允许在流动性修改前后执行自定义逻辑。

 先看代码:

function modifyLiquidity(
        PoolKey memory key,
        IPoolManager.ModifyLiquidityParams memory params,
        bytes calldata hookData
    ) external onlyWhenUnlocked noDelegateCall returns (BalanceDelta callerDelta, BalanceDelta feesAccrued) {
        PoolId id = key.toId();
        {
            Pool.State storage pool = _getPool(id);
            pool.checkPoolInitialized();

            key.hooks.beforeModifyLiquidity(key, params, hookData);

            BalanceDelta principalDelta;
            (principalDelta, feesAccrued) = pool.modifyLiquidity(
                Pool.ModifyLiquidityParams({
                    owner: msg.sender,
                    tickLower: params.tickLower,
                    tickUpper: params.tickUpper,
                    liquidityDelta: params.liquidityDelta.toInt128(),
                    tickSpacing: key.tickSpacing,
                    salt: params.salt
                })
            );

            // fee delta and principal delta are both accrued to the caller
            callerDelta = principalDelta + feesAccrued;
        }

        // event is emitted before the afterModifyLiquidity call to ensure events are always emitted in order
        emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta, params.salt);

        BalanceDelta hookDelta;
        (callerDelta, hookDelta) = key.hooks.afterModifyLiquidity(key, params, callerDelta, feesAccrued, hookData);

        // if the hook doesn't have the flag to be able to return deltas, hookDelta will always be 0
        if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) _accountPoolBalanceDelta(key, hookDelta, address(key.hooks));

        _accountPoolBalanceDelta(key, callerDelta, msg.sender);
    }

入参

  • PoolKey memory key

表示池的唯一标识,这里面池的基本信息(代币对、手续费、tick 间距等)。通过 key.toId() 转换为池的唯一 ID。

  • IPoolManager.ModifyLiquidityParams memory params

    具体参数如下:
    • salt:用于唯一标识流动性位置的附加参数。
    • liquidityDelta:流动性变化量(正值表示增加流动性,负值表示减少流动性)。
    • tickUpper:流动性范围的上界。
    • tickLower:流动性范围的下界。
  • bytes calldata hookData

    钩子(Hooks)所需的附加数据,允许在流动性修改前后执行自定义逻辑。

这些参数都比较简单,唯一的需要解释的是salt参数,这里包含一个v4版本相对v3版本的升级,在v3版本中,增减流动性只需要提供tick的范围,合约根据发送者的地址和tick范围就可以定位到用户所拥有的流动性,并对其做增减。但是我们试想这样一个场景。用户可能希望在相同的价格范围内应用不同的策略,一部分资金用于长期流动性提供,另一部分用于短期套利。这样的话在同一个tick范围内统一做流动性管理就不太合适了。所以uniswap v4版本在此基础上对于同一用户,同一tick范围的流动性又做了一层分区,允许用户在不同的分区采取不同的交易策略,而这些分区就是用salt来区分位置的。需要注意的是salt 是一个 用户定义的参数,Uniswap 并不会自动生成 salt。用户需要自己管理它。

前面几行代码比较简单

  • Pool.State storage pool = _getPool(id); //获取相应的pool
  • pool.checkPoolInitialized();//校验当前pool是否初始化
  • key.hooks.beforeModifyLiquidity(key, params, hookData);//执行更新流动性的相应钩子

更新pool

这个方法的核心步骤就是

(principalDelta, feesAccrued) = pool.modifyLiquidity( Pool.ModifyLiquidityParams({
                                    owner: msg.sender,
                                    tickLower: params.tickLower,
                                    tickUpper: params.tickUpper,
                                    liquidityDelta: params.liquidityDelta.toInt128(),
                                    tickSpacing: key.tickSpacing,
                                    salt: params.salt
                                }));

我们仔细分析一下:

function modifyLiquidity(State storage self, ModifyLiquidityParams memory params)
        internal
        returns (BalanceDelta delta, BalanceDelta feeDelta)
    {
        int128 liquidityDelta = params.liquidityDelta;
        int24 tickLower = params.tickLower;
        int24 tickUpper = params.tickUpper;
        checkTicks(tickLower, tickUpper);

        {
            ModifyLiquidityState memory state;

            // if we need to update the ticks, do it
            if (liquidityDelta != 0) {
                (state.flippedLower, state.liquidityGrossAfterLower) =
                    updateTick(self, tickLower, liquidityDelta, false);
                (state.flippedUpper, state.liquidityGrossAfterUpper) = updateTick(self, tickUpper, liquidityDelta, true);

                // `>` and `>=` are logically equivalent here but `>=` is cheaper
                if (liquidityDelta >= 0) {
                    uint128 maxLiquidityPerTick = tickSpacingToMaxLiquidityPerTick(params.tickSpacing);
                    if (state.liquidityGrossAfterLower > maxLiquidityPerTick) {
                        TickLiquidityOverflow.selector.revertWith(tickLower);
                    }
                    if (state.liquidityGrossAfterUpper > maxLiquidityPerTick) {
                        TickLiquidityOverflow.selector.revertWith(tickUpper);
                    }
                }

                if (state.flippedLower) {
                    self.tickBitmap.flipTick(tickLower, params.tickSpacing);
                }
                if (state.flippedUpper) {
                    self.tickBitmap.flipTick(tickUpper, params.tickSpacing);
                }
            }

            {
                (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
                    getFeeGrowthInside(self, tickLower, tickUpper);

                Position.State storage position = self.positions.get(params.owner, tickLower, tickUpper, params.salt);
                (uint256 feesOwed0, uint256 feesOwed1) =
                    position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);

                // Fees earned from LPing are calculated, and returned
                feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128());
            }

            // clear any tick data that is no longer needed
            if (liquidityDelta < 0) {
                if (state.flippedLower) {
                    clearTick(self, tickLower);
                }
                if (state.flippedUpper) {
                    clearTick(self, tickUpper);
                }
            }
        }

        if (liquidityDelta != 0) {
            Slot0 _slot0 = self.slot0;
            (int24 tick, uint160 sqrtPriceX96) = (_slot0.tick(), _slot0.sqrtPriceX96());
            if (tick < tickLower) {
                // current tick is below the passed range; liquidity can only become in range by crossing from left to
                // right, when we'll need _more_ currency0 (it's becoming more valuable) so user must provide it
                delta = toBalanceDelta(
                    SqrtPriceMath.getAmount0Delta(
                        TickMath.getSqrtPriceAtTick(tickLower), TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta
                    ).toInt128(),
                    0
                );
            } else if (tick < tickUpper) {
                delta = toBalanceDelta(
                    SqrtPriceMath.getAmount0Delta(sqrtPriceX96, TickMath.getSqrtPriceAtTick(tickUpper), liquidityDelta)
                        .toInt128(),
                    SqrtPriceMath.getAmou
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值