Uniswap v4/v3 computeSwapStep 方法解析

computeSwapStep 是 Uniswap v4/v3 swap 撮合的核心单步计算方法,用于在一个 tick 区间内,给定当前价格、目标价格、流动性、剩余兑换量和手续费,计算本 step 的价格变化、实际兑换数量和手续费。

    function computeSwapStep(
        uint160 sqrtPriceCurrentX96,
        uint160 sqrtPriceTargetX96,
        uint128 liquidity,
        int256 amountRemaining,
        uint24 feePips
    ) internal pure returns (uint160 sqrtPriceNextX96, uint256 amountIn, uint256 amountOut, uint256 feeAmount) {
        unchecked {
            uint256 _feePips = feePips; // upcast once and cache
            bool zeroForOne = sqrtPriceCurrentX96 >= sqrtPriceTargetX96;
            bool exactIn = amountRemaining < 0;

            if (exactIn) {
                uint256 amountRemainingLessFee =
                    FullMath.mulDiv(uint256(-amountRemaining), MAX_SWAP_FEE - _feePips, MAX_SWAP_FEE);
                amountIn = zeroForOne
                    ? SqrtPriceMath.getAmount0Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, true)
                    : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, true);
                if (amountRemainingLessFee >= amountIn) {
                    // `amountIn` is capped by the target price
                    sqrtPriceNextX96 = sqrtPriceTargetX96;
                    feeAmount = _feePips == MAX_SWAP_FEE
                        ? amountIn // amountIn is always 0 here, as amountRemainingLessFee == 0 and amountRemainingLessFee >= amountIn
                        : FullMath.mulDivRoundingUp(amountIn, _feePips, MAX_SWAP_FEE - _feePips);
                } else {
                    // exhaust the remaining amount
                    amountIn = amountRemainingLessFee;
                    sqrtPriceNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput(
                        sqrtPriceCurrentX96, liquidity, amountRemainingLessFee, zeroForOne
                    );
                    // we didn't reach the target, so take the remainder of the maximum input as fee
                    feeAmount = uint256(-amountRemaining) - amountIn;
                }
                amountOut = zeroForOne
                    ? SqrtPriceMath.getAmount1Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, false)
                    : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, false);
            } else {
                amountOut = zeroForOne
                    ? SqrtPriceMath.getAmount1Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, false)
                    : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, false);
                if (uint256(amountRemaining) >= amountOut) {
                    // `amountOut` is capped by the target price
                    sqrtPriceNextX96 = sqrtPriceTargetX96;
                } else {
                    // cap the output amount to not exceed the remaining output amount
                    amountOut = uint256(amountRemaining);
                    sqrtPriceNextX96 =
                        SqrtPriceMath.getNextSqrtPriceFromOutput(sqrtPriceCurrentX96, liquidity, amountOut, zeroForOne);
                }
                amountIn = zeroForOne
                    ? SqrtPriceMath.getAmount0Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, true)
                    : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, true);
                // `feePips` cannot be `MAX_SWAP_FEE` for exact out
                feeAmount = FullMath.mulDivRoundingUp(amountIn, _feePips, MAX_SWAP_FEE - _feePips);
            }
        }
    }

参数说明

入参

  • sqrtPriceCurrentX96:当前池子的 sqrt(price)(Q64.96 格式)
  • sqrtPriceTargetX96:本 step 允许到达的目标 sqrt(price)(tick 边界或用户价格极限)
  • liquidity:当前 tick 区间内的流动性
  • amountRemaining:剩余要兑换的数量(正数 exact output,负数 exact input)
  • feePips:手续费(以百万分之一为单位)

返回

  • sqrtPriceNextX96:本 step 结束后的价格
  • amountIn:本 step 实际消耗的输入币数量
  • amountOut:本 step 实际获得的输出币数量
  • feeAmount:本 step 实际收取的手续费

逻辑解析

首先是判断方向和模式

  • zeroForOne = sqrtPriceCurrentX96 >= sqrtPriceTargetX96
    方向:价格下降代表:token0 → token1;否则反之
  • exactIn = amountRemaining < 0
    模式:exact input(指定输入)还是 exact output(指定输出)

本文只解析指定输入的模式exact input(amountRemaining < 0

先计算去掉手续费后,实际可用的输入量 amountRemainingLessFee

uint256 amountRemainingLessFee =
                    FullMath.mulDiv(uint256(-amountRemaining), MAX_SWAP_FEE - _feePips, MAX_SWAP_FEE);

 我们拆解以下这段代码

  • amountRemaining:用户本次 swap 还剩下要兑换的输入数量(exact input 时为负数,所以取负号变成正数)。
  • MAX_SWAP_FEE:最大手续费分母,等于 1e6(代表 100%)。
  • _feePips:实际手续费(如 3000 表示 0.3%)。
  • MAX_SWAP_FEE - _feePips:兑换后剩下的比例(如 997000,表示 99.7%)。
  • FullMath.mulDiv(a, b, c):高精度计算 (a * b) / c,防止溢出。

这行代码等价于:

amountRemainingLessFee = amountRemaining * (MAX_SWAP_FEE - _feePips) / MAX_SWAP_FEE

进一步简化就是:

amountRemainingLessFee = amountRemaining * (1 - fee)

假设用户输入 1000 USDC,手续费 0.3%(_feePips = 3000);amountRemaining = -1000(exact input 语义,负数);MAX_SWAP_FEE = 1_000_000

则:

amountRemainingLessFee = 1000 * (1_000_000 - 3000) / 1_000_000
                      = 1000 * 997_000 / 1_000_000
                      = 997

接着计算算如果价格从当前 sqrtPrice 变化到目标 sqrtPrice,所需要的输入币数量(amountIn)是多少。

amountIn = zeroForOne
    ? SqrtPriceMath.getAmount0Delta(sqrtPriceTargetX96, sqrtPriceCurrentX96, liquidity, true)
    : SqrtPriceMath.getAmount1Delta(sqrtPriceCurrentX96, sqrtPriceTargetX96, liquidity, true);

getAmountXDelta的代码比较简单,这里直接上公式和推导:

getAmount0Delta:amountIn = liquidity * |sqrtPriceBX96 - sqrtPriceAX96| / 2^96

getAmount1Delta:amountIn = liquidity /|sqrtPriceBX96 - sqrtPriceAX96| / 2^96

 推导步骤:

amount0* amount1 = L^2 ===> \sqrt{amount1 }*\sqrt{amount0}= L   

sqrtPrice =\sqrt{\frac{amount1}{amount0}}

于是得出:

amount1=L*sqrtPrice

amount0=L/sqrtPrice

所以:
\Delta amount1=L*\left | sqrtPriceB- sqrtPriceA\right |

\Delta amount0=L/\left | sqrtPriceB- sqrtPriceA\right |

后面/ 2^96的原因是sqrtPriceX96=sqrtPrice*2^96,所以需要还原回去。

如果去除手续费的输入可以覆盖价格变化所需要的输入,直接返回sqrtPriceTargetX96,之前计算的amountIn,并计算相应的手续费

if (amountRemainingLessFee >= amountIn) {
    // `amountIn` is capped by the target price
    sqrtPriceNextX96 = sqrtPriceTargetX96;
    feeAmount = _feePips == MAX_SWAP_FEE ? 
                   amountIn
                   : FullMath.mulDivRoundingUp(amountIn, _feePips, MAX_SWAP_FEE -_feePips);
}

如果当前输入的金额无法让价格到达目标价格则需要计算当前的输入金额到达的价格。

else {
    // exhaust the remaining amount
    amountIn = amountRemainingLessFee;
    sqrtPriceNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput(
                        sqrtPriceCurrentX96, liquidity, amountRemainingLessFee, zeroForOne
                    );
    // we didn't reach the target, so take the remainder of the maximum input as fee
    feeAmount = uint256(-amountRemaining) - amountIn;
}

如果去除手续费后的输入金额不足以让价格到达目标价格,价格只能到达中间某个点则需要通过getNextSqrtPriceFromInput计算当前的输入金额可以到达的价格是多少。

getNextSqrtPriceFromInput这个方法的代码并不复杂,这里只介绍其公式推导。公式如下:

sqrtPx96_next = L*2^96 * sqrtPx96 / (L*2^96 ± amount * sqrtPx96)

  • 输入 amount 个 token0,x 变为 x' = x ± amount
  • 新价格 sqrtP_next 满足:x' = L / sqrtP_next
  • x ± amount = L / sqrtP_next
  • L / sqrtP ± amount = L / sqrtP_next
  • sqrtP_next = L/ (L / sqrtP ± amount)=L * sqrtP / (L ± amount * sqrtP)

由于 Uniswap 内部 sqrtP 用 Q64.96 定点数表示,左右两边同时乘以 2^96

sqrtPx96_next = L * sqrtPx96 / (L ± amount * sqrtP)

进一步把sqrtP转换成sqrtPx96得出:

sqrtPx96_next = L * sqrtPx96 / (L ± amount * sqrtPx96/ 2^96)

等式右边分子分母同时乘以2^96,最终得到

sqrtPx96_next = L*2^96 * sqrtPx96 / (L*2^96 ± amount * sqrtPx96)

getNextSqrtPriceFromInput这个方法就是完全按照这一步公式实现的,有兴趣的同学可以自行查阅!

amountRemainingLessFee作为去除手续费后的输入,feeAmount = uint256(-amountRemaining) - amountIn;就是本步交易完后需要花费的手续费。

amountOut = zeroForOne
                    ? SqrtPriceMath.getAmount1Delta(sqrtPriceNextX96, sqrtPriceCurrentX96, liquidity, false)
                    : SqrtPriceMath.getAmount0Delta(sqrtPriceCurrentX96, sqrtPriceNextX96, liquidity, false);

最后计算amountOut和公式和之前计算amountIn一致,这里不再赘述!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值