第一笔交易

计算交易数量

  1. 计算过程中的流动性保持不变
  2. 需要根据当前流动性值和当前价格,根据输入的 tokens 数量计算目标价格
  3. 根据目标价格和当前价格,计算另一种代币的输入/输出数量
  4. 执行 swap 后,更新当前池子的兑换价格和 tick 位置

首先,需要知道如何计算交易出入的数量。 同样,我们在这小节中也会硬编码我们希望交易的 USDC 数额,这里我们选择 42 USDC swap ETH

在决定了我们希望投入的资金量之后,需要计算我们会获得多少 ETH token。 在 UniswapV2 中,我们会使用当前池子的资产数量来计算, 但是在 V3 中我们有 $L$ 和 $\sqrt{P}$, 并且我们知道在交易过程中,$L$ 保持不变而只有 $\sqrt{P}$ 发生变化 (当在同一区间内进行交易时,V3 的表现和 V2 一致)。我们还知道如下公式:

$$L = \frac{\Delta y}{\Delta \sqrt{P}}$$

并且,在这里我们知道了$\Delta y$。它正是我们希望投入的 42 USDC。 因此,我们可以根据公式得出投入的 42 USDC 会对价格造成多少影响:

$$\Delta \sqrt{P} = \frac{\Delta y}{L}$$

V3 中,我们得到了我们交易导致的价格变动(回忆一下,交易使得现价沿着曲线移动)。 知道了目标价格(target price),合约可以计算出投入 USDC 的数量和输出 ETH 的数量。

注意,$\sqrt{P}$ 用 Q64.96 定点数表示

我们将数字代入上述公式:

$$\Delta \sqrt{P} = \frac{42 \enspace USDC}{1517882343751509868544} * 2^{96} = 2192253463713690532467206957$$

把差价加到现在的 $\sqrt{P}$,我们就能得到目标价格:

USDC 兑换 ETH 会减少池子中 ETH 的数量,因此 ETH 的价格会上涨

$$\sqrt{P_{target}} - \sqrt{P_{current}} = \Delta \sqrt{P}$$

$$\sqrt{P_{target}} = \sqrt{P_{current}} + \Delta \sqrt{P}$$

$$\sqrt{P_{target}} = 5602277097478614198912276234240 + 2192253463713690532467206957 = 5604469350942327889444743441197$$

在 Python 中进行相应计算:

amount_in = 42 * eth
price_diff = (amount_in * q96) // liq
price_next = sqrtp_cur + price_diff
print("New price:", (price_next / q96) ** 2)
print("New sqrtP:", price_next)
print("New tick:", price_to_tick((price_next / q96) ** 2))
# New price: 5003.913912782393
# New sqrtP: 5604469350942327889444743441197
# New tick: 85184

知道了目标价格,我们就能计算出投入 USDC 的数量和获得 ETH 的数量:

$$\Delta x = \Delta \frac{L}{\sqrt{p}} / 2^{96} = L * (\frac{1}{\sqrt{p_b}} - \frac{1}{\sqrt{p_c}}) / 2 ^{96} = \frac{L(\sqrt{p_b}-\sqrt{p_c})}{\sqrt{p_b}\sqrt{p_c}} / 2 ^ {96}$$ $$\Delta y = L(\sqrt{p_b}-\sqrt{p_c}) / 2 ^{96} $$

用Python:

amount_in = calc_amount1(liq, price_next, sqrtp_cur)
amount_out = calc_amount0(liq, price_next, sqrtp_cur)

print("USDC in:", amount_in / eth)
print("ETH out:", amount_out / eth)
# USDC in: 42.0
# ETH out: 0.008396714242162444

我们使用另一个公式验证一下:

$$\Delta x = \Delta \frac{1}{\sqrt{P}} L$$

使用上述公式,在知道价格变动和流动性数量的情况下,我们能求出我们购买了多少 ETH,也即 $\Delta x$。一个需要注意的点是: $\Delta \frac{1}{\sqrt{P}}$ 不等于 $\frac{1}{\Delta \sqrt{P}}$!前者才是 ETH 价格的变动,并且能够用如下公式计算:

$$\Delta \frac{1}{\sqrt{P}} = \frac{1}{\sqrt{P_{target}}} - \frac{1}{\sqrt{P_{current}}}$$

我们知道了公式里面的所有数值,接下来将其带入即可(可能会在显示上有些问题):

$$\Delta \frac{1}{\sqrt{P}} = \frac{1}{5604469350942327889444743441197} - \frac{1}{5602277097478614198912276234240}$$

$$\Delta \frac{1}{\sqrt{P}} = -0.00000553186106731426$$

接下来计算 $\Delta x$:

$$\Delta x = -0.00000553186106731426 * 1517882343751509868544 = -8396714242162698 $$

即 0.008396714242162698 ETH,这与我们第一次算出来的数量非常接近!注意到这个结果是负数,因为我们是从池子中提出 ETH。

实现swap

交易在 swap 函数中实现:

function swap(address recipient)
    public
    returns (int256 amount0, int256 amount1)
{

此时,它仅仅接受一个 recipient 参数,即提出 token 的接收者。

首先,我们需要计算出目标价格和对应 tick,以及 token 的数量。 同样,我们将会在这里硬编码我们之前计算出来的值:

int24 nextTick = 85184;
uint160 nextPrice = 5604469350942327889444743441197;

amount0 = -0.008396714242162444 ether;
amount1 = 42 ether;

接下来,我们需要更新现在的 tick 和对应的 sqrtP

(slot0.tick, slot0.sqrtPriceX96) = (nextTick, nextPrice);

然后,manager 合约把对应的 token 发送给 recipient 并且让调用者将需要的 token 转移到本合约:

IERC20(token0).transfer(recipient, uint256(-amount0));

uint256 balance1Before = balance1();
IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(
    amount0,
    amount1
);
if (balance1Before + uint256(amount1) < balance1())
    revert InsufficientInputAmount();

我们使用 callback 函数来将控制流转移到调用者, 让它转入 token,之后我们需要通过检查确认 caller 转入了正确的数额。

最后,合约释放出一个 swap 事件,使得该笔交易能够被监听到。这个事件包含了所有有关这笔交易的信息:

emit Swap(
    msg.sender,
    recipient,
    amount0,
    amount1,
    slot0.sqrtPriceX96,
    liquidity,
    slot0.tick
);

本章中所有用到的 python 计算都在 unimath.py

# Online Python compiler (interpreter) to run Python online.
# Write Python 3 code in this online editor and run it.
# Online Python compiler (interpreter) to run Python online.
# Write Python 3 code in this online editor and run it.
import math

min_tick = -887272
max_tick = 887272

q96 = 2**96
ETH = 10**18


def price_to_tick(p):
    return math.floor(math.log(p, 1.0001))


def price_to_sqrtp(p):
    return int(math.sqrt(p) * q96)


def sqrtp_to_price(sqrtp):
    return (sqrtp / q96) ** 2


def tick_to_sqrtp(t):
    return int((1.0001 ** (t / 2)) * q96)


def liquidity0(amount, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return (amount * (pa * pb) / q96) / (pb - pa)


def liquidity1(amount, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return amount * q96 / (pb - pa)


def calc_amount0(liq, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return int(liq * q96 * (pb - pa) / pb / pa)


def calc_amount1(liq, pa, pb):
    if pa > pb:
        pa, pb = pb, pa
    return int(liq * (pb - pa) / q96)


# Liquidity provision
price_low = 0.7
price_cur = 1
price_upp = 1.2

sqrtp_low = price_to_sqrtp(price_low)
sqrtp_cur = price_to_sqrtp(price_cur)
sqrtp_upp = price_to_sqrtp(price_upp)

print(f"Price range: {sqrtp_low}-{sqrtp_upp}; current price: {sqrtp_cur}")

amount_ETH = 100 * ETH
amount_usdc = 100 * ETH
print(f"预计Deposit: {amount_ETH/ETH} ETH, {amount_usdc/ETH} USDC;")


liq = 0

if price_cur <= price_low:
    liq = liquidity0(amount_ETH, sqrtp_cur, sqrtp_upp)
    amount0 = calc_amount0(liq, sqrtp_upp, sqrtp_cur)
    print(f"只收取Token0,实际Deposit: {amount0/ETH} ETH, liquidity: {liq}")
elif price_cur < price_upp:
    liq0 = liquidity0(amount_ETH, sqrtp_cur, sqrtp_upp)
    liq1 = liquidity1(amount_usdc, sqrtp_cur, sqrtp_low)
    liq = int(min(liq0, liq1))
    amount0 = calc_amount0(liq, sqrtp_upp, sqrtp_cur)
    amount1 = calc_amount1(liq, sqrtp_low, sqrtp_cur)
    print(f"实际Deposit: {amount0/ETH} ETH, {amount1/ETH} USDC; liquidity: {liq}")
    amount_in = calc_amount1(liq, sqrtp_upp, sqrtp_cur)
    print(f"买空ETH,USDC in:{amount_in / ETH}, USDC Left:{amount_in / ETH + amount1/ETH}")
    amount_out = calc_amount0(liq, sqrtp_low, sqrtp_cur)
    print(f"买空USDC,ETH In:{ amount_out / ETH},ETH Left:{ amount_out / ETH + amount0/ETH}",)
else :
    liq = liquidity1(amount_usdc, sqrtp_cur, sqrtp_low)
    amount1 = calc_amount1(liq, sqrtp_low, sqrtp_cur)
    print(f"只收取Token1,实际Deposit: {amount1/ETH} USDC; liquidity: {liq}")


# Swap USDC for ETH
amount_in = 90 * ETH

print(f"\nSelling {amount_in/ETH} USDC")

price_diff = (amount_in * q96) // liq
price_next = sqrtp_cur + price_diff

print("New price:", (price_next / q96) ** 2)
print("New sqrtP:", price_next)
print("New tick:", price_to_tick((price_next / q96) ** 2))

amount_in = calc_amount1(liq, price_next, sqrtp_cur)
amount_out = calc_amount0(liq, price_next, sqrtp_cur)

print("USDC in:", amount_in / ETH)
print("ETH out:", amount_out / ETH)

# Swap ETH for USDC
amount_in = 85 * ETH

print(f"\nSelling {amount_in/ETH} ETH")

price_next = int((liq * q96 * sqrtp_cur) // (liq * q96 + amount_in * sqrtp_cur))

print("New price:", (price_next / q96) ** 2)
print("New sqrtP:", price_next)
print("New tick:", price_to_tick((price_next / q96) ** 2))

amount_in = calc_amount0(liq, price_next, sqrtp_cur)
amount_out = calc_amount1(liq, price_next, sqrtp_cur)

print("ETH in:", amount_in / ETH)
print("USDC out:", amount_out / ETH)

输出:

Price range: 66287036551430451535630303232-86790103597495583603102318592; current price: 79228162514264337593543950336
预计Deposit: 100.0 ETH, 100.0 USDC;
实际Deposit: 53.34216051094175 ETH, 100.0 USDC; liquidity: 612220008844691898368
买空ETH,USDC in:58.433409155808185, USDC Left:158.4334091558082
买空USDC,ETH In:119.52286093343936,ETH Left:172.8650214443811

Selling 90.0 USDC
New price: 1.3156227092534618
New sqrtP: 90875175880814835159951393049
New tick: 2743
USDC in: 90.0
ETH out: 78.46515351602369

Selling 85.0 ETH
New price: 0.7710372403582774
New sqrtP: 69569240325740947748657834520
New tick: -2601
ETH in: 85.0
USDC out: 74.63741730250688

Preference

https://www.rapidtables.com/calc/math/log-calculator.html

https://www.programiz.com/python-programming/online-compiler/