乐于分享
好东西不私藏

DeFi 源码逐行读 #14 | 单步交换计算引擎:UniswapV3 SwapMath.sol

DeFi 源码逐行读 #14 | 单步交换计算引擎:UniswapV3 SwapMath.sol

朋友们大家好!今天我们来分析SwapMath.sol。这是swap的核心运算库,主要完成了单步交换计算。

入参五个字段,分别是

sqrtRatioCurrentX96:当前价格;

sqrtRatioTargetX96:限定价格,通常是tick边界或者用户要求的限价;

liquidity:当前流动性

amountRemaining:仍需swap的金额

feePips:手续费率,比如3000是指0.3%;

出参四个字段分别是:

sqrtRatioNextX96:这一步交换之后的价格;

amountIn:实际消耗的输入代币数量;

amountOut:实际产出的代币数量;

feeAmount:手续费

37行定义了一个变量叫zeroForOne,代表了代币交换方向,true代表用token0换token1,false代表用token1换token0;

38行的exactIn也是一个布尔类型的变量,当为true时,意味着用户希望输入值确定,比如用完这100块钱,买到几斤肉算几斤肉;当exactIn为false时,意味着用户希望输出值确定,也就是就要买10斤肉,需要花多少钱算多少钱。

40行,当exactIn为true时进入第一个分支。

amountRemainingLessFee是去除手续费之后仍可兑换的金额,主要完成的计算是amountRemaining*(1-手续费率),feePips的单位是 0.0001%,所以用1e6作为基准。

42行的amountIn是需要消耗的代币数量。当zeroForOne为true时,意味着将token0换为token1,所以调用getAmount0Delta计算需要消耗的token0的数量。反之如果zeroForOne为false,就计算需要消耗的token1的数量。

如果计算出的amountIn比amountRemainingLessFee小,也就是在限价范围内把所有的流动性都耗尽了,直接把价格推到了sqrtRatioTargetX96限价或者tick边界上。

如果amountIn比amountRemainingLessFee大,意味着其实不用到更远的价格上,所以需要精确计算到中间哪个价格就够用。

调用getNextSqrtPriceFromInput函数,可以参见这个DeFi 源码逐行读 #11 | 定价与数量转换:UniswapV3 SqrtPriceMath.sol里关于函数的解析,这个函数主要是为了计算在代币数量变化量和流动性一定的前提下,价格变化的结果。因此调用这个函数可以更精确地算出来价格推到哪里就够完成amountIn的兑换。

分支二,当exactIn为false,当输出量为确定值时,需要比较的是amountOut和待兑换代币数量的关系。大致逻辑和分支一类似,只不过在对应zeroForOne的判定中,由于算的输出代币所以zeroForOne为真,意味着输出代币是token1,因此调用的是getAmount1Delta。

67行,如果下一个价格恰好等于sqrtRatioTargetX96用户设定的限价,意味着价格完全被推到了目标价;如果max为false说明价格没有被推得太远就已经兑换完成了。

接下来详细计算输入或者输出结果。

这里有两个变量同步在控制,token0换token1还是token1换token0;定值输入还是定值输出。两两相乘一共有四种情况,再乘上是否max为真(耗尽至限定价格)一共有8种情况,如果价格耗尽直接输出上一步计算出amountIn或者amountOut;如果价格尚未耗尽就用上一步计算出来的抵达价格再计算对应的代币数量。

在精确输出情况下,由于边界取整的原因,可能实际算出来的输出代币略大于用户设定值,88行做了个截断动作,确保不多给。

最后计算手续费

如果价格没有推到限价处,钱就花完了,那么直接用amountRemaining) - amountIn计算手续费;其他情况用户的钱都花完了,所以可以直接计算手续费。

总结

这是一个做swap计算的库,主要目的是完成给定数量的代币在限价条件下可兑换数量的计算,同步计算手续费。后续在pool中会作为swap主循环中的原子计算单元参与计算。