+2

Cũng dễ. Phần 3: Swap hoy.

Phần trước mình đã làm rõ các 2 công thức để tính amountToken0 và amountToken1 mà provider phải cho vào khi thêm thanh khoản. Bây giờ chúng ta sẽ làm rõ việc việc swap, các con số sẽ tính toán như thế nào. Nhắc lại các công thức:

  1. L=xyL = \sqrt{xy}

  2. P=yxP = \frac{y}{x}

  3. Δy=LΔP\Delta{y} = L* \Delta{\sqrt{P}}

  4. Δx=LΔ1P\Delta{x} = L * \Delta{\frac{1}{\sqrt{P}}}

3. Swap

3.1 Swap trong 1 price-range

Ví dụ 1: Swap token1 lấy token0

Đầu tiên để đơn giản, chúng ta sẽ xét đến việc swap trong price-range trước. Current-price-range đang có thanh khoản là LL, current-price là P0P_0.

Ví dụ, Alice đang muốn swap một lượng Δy\Delta{y} token1 lấy một lấy token0, chúng ta cần tính xem lượng Δx\Delta{x} token0 mà Alice sẽ nhận được là bao nhiêu:

image.png

Chúng ta có các tham số đã biết sau:

  • Δy\Delta{y}
  • P0P_0 => biết được luôn P0\sqrt{P_0}
  • LL

Các bước tính toán như sau:

  • Bước 1: Sử dụng công thức (3)(3) để tính ΔP\Delta{\sqrt{P}}

ΔP=ΔyL\Delta{\sqrt{P}} = \frac{\Delta{y}}{L}

  • Bước 2: Do đã biết được P0\sqrt{P_0}ΔP\Delta{\sqrt{P}}, dễ dàng suy ra được P1\sqrt{P_1}

P1=P0+ΔP\sqrt{P_1} = \sqrt{P_0} + \Delta{\sqrt{P}}

  • Bước 3: biết được P0\sqrt{P_0}P1\sqrt{P_1}, thay vào công thức (4)(4) để tính ra được Δx\Delta{x}

Δx=L(P0P1P1P0)\Delta{x} = L * (\frac{\sqrt{P_0} - \sqrt{P_1}}{\sqrt{P_1} \sqrt{P_0}})

Nếu để ý, Δx\Delta{x} sẽ ra kết quả âm, điều này thể hiện là sau khi swap, lượng token0 trong Pool sẽ giảm đi một lượng Δx|\Delta{x}|, nghĩa là sẽ chuyển Δx|\Delta{x}| token0 từ Pool cho Alice.

Đối chiếu vào code ta sẽ có:

  • Bước 1+2, chính là hàm getNextSqrtPriceFromAmount1RoundingDown() trong file libraries/SqrtPriceMath.sol, đừng lăn tăn về FixedPoint96.RESOLUTIONFixedPoint96.Q96 mình sẽ nói về chúng ở tập sau: image.png

  • Bước 3, chính là hàm getAmount0Delta() mà mình đã nói ở phần trước

Ví dụ 2: Swap token0 lấy token1

Ví dụ, Bob đang muốn swap 1 lượng Δx\Delta{x} token0 lấy token1. Hãy tính toán lượng Δy\Delta{y} token1 mà Bob sẽ nhận được: image.png

Chúng ta có các tham số đã biết sau:

  • Δx\Delta{x}
  • P0P_0 => biết được luôn P0\sqrt{P_0}
  • LL

Các bước tính toán như sau:

  • Bước 1: Sử dụng công thức (4)(4) để tính P1\sqrt{P_1}

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

<=>Δx=L(1P11P0)<=> \Delta{x} = L(\frac{1}{\sqrt{P_1}} - \frac{1}{\sqrt{P_0}})

<=>LP1=Δx+LP0<=> \frac{L}{\sqrt{P_1}} = \Delta{x} + \frac{L}{\sqrt{P_0}}

<=>LP1=ΔxP0+LP0<=> \frac{L}{\sqrt{P_1}} = \frac{\Delta{x}\sqrt{P_0} + L}{\sqrt{P_0}}

<=>P1=LP0ΔxP0+L<=> \sqrt{P_1} = \frac{L\sqrt{P_0}}{\Delta{x}\sqrt{P_0} + L}

<=>P1=LΔx+LP0<=> \sqrt{P_1} = \frac{L}{\Delta{x} + \frac{L}{\sqrt{P_0}}}

  • Bước 2: Sử dụng công thức (3)(3) để tính Δy\Delta{y}:

Δy=L(P1P0)\Delta{y} = L * (\sqrt{P_1} - \sqrt{P_0})

Tương tự như ví dụ 1, Δy\Delta{y} cũng sẽ ra kết quả âm, điều này thể hiện Δy|\Delta{y}| sẽ là lượng token1 mà Pool sẽ chuyển cho Bob.

Đối chiếu vào code ta sẽ có:

  • Bước 1, chính là hàm getNextSqrtPriceFromAmount0RoundingUp() trong libraries/SqrtPriceMath.sol. Hàm này tính P1=LΔx+LP0\sqrt{P_1} = \frac{L}{\Delta{x} + \frac{L}{\sqrt{P_0}}}. Tại sao? Điều này mình sẽ giải thích cùng với FixedPoint96.RESOLUTION vào tập sau: image.png
  • Bước 2, chính là hàm getAmount1Delta() mà mình đã nói ở phần trước

3.2 Swap cross qua các price-range

Ví dụ trên là trong trường hợp lệnh swap không làm giá vượt ra khởi current-price-range. Vậy nếu current-price-range không đủ thanh khoản thì contract sẽ xử lý như thế nào?

Ví dụ 1: Swap token1 lấy token0 image.png

Alice đang muốn swap một lượng Δy\Delta{y} token1 lấy token0. Giả sử:

  • current-price P0P_0 đang ở price range D-E
  • lượng token0 còn lại trong price-range D-E là Δx1\Delta{x}_1 chỉ đủ để fill một lượng Δy1\Delta{y}_1, và Δy1<Δy\Delta{y}_1 < \Delta{y}
  • như vậy nghĩa là, ở bước swap đầu tiên, Alice đã sử dụng hết token0 của D-E mà D-E vẫn chưa hấp thu hết lượng token1 mà Alice đã cho vào, vẫn còn thừa một lượng Δy2\Delta{y}_2 phải nhờ đến price-range E-F hấp thụ.
  • thật may là E-F chỉ cần tốn một lượng Δx2\Delta{x}_2 để hấp thụ hết Δy2\Delta{y}_2

Bây giờ sẽ tính các con số Δx1\Delta{x}_1 Δx2\Delta{x_2} token0 mà Alice sẽ nhận được sau 2 bước swap. Lưu ý, tổng lượng token0 mà Alice sẽ nhận được là Δx1+Δx2\Delta{x_1} + \Delta{x_2}

  • Bước 1: Tính Δx1\Delta{x}_1Δy2\Delta{y}_2. Do chúng ta đã biết chắc rằng toàn bộ token0 của D-E sẽ bị hấp thụ hết, nên chúng ta sẽ sử dụng cho vào công thức PEP_E. Sử dụng công thức (4) để tính Δx1\Delta{x}_1, Δy1\Delta{y}_1 như sau

Δx1=L(P0PEP0PE)\Delta{x}_1 = L * (\frac{\sqrt{P_0} - \sqrt{P_E}}{\sqrt{P_0} \sqrt{P_E}})

Δy1=L(PEP0)\Delta{y}_1 = L * (\sqrt{P_E} - \sqrt{P_0})

=>Δy2=ΔyΔy1=> {\Delta{y}_2} = \Delta{y} - \Delta{y}_1

  • Bước 2 : Tính P2P_2. Do chúng ta đã biết chắc rằng toàn bộ phần thừa Δy2\Delta{y}_2 sẽ bị hấp thu hết trong E-F, nên P2P_2 sẽ nằm trong E-F. Sử dụng công thức (3) để tính P2P_2

LEF=L+liquidityNetEL_{E-F} = L + liquidityNet_E

Δy2=LEF(P2PE)\Delta{y}_2 = L_{E-F} * (P_2 - P_E)

<=>P2=Δy2LEF+PE<=> P_2 = \frac{\Delta{y}_2}{L_{E-F}} + P_E

  • Bước 3: Tính Δx2\Delta{x}_2. Do đã biết P2P_2. Sử dụng công thức 4 để tính:

Δx2=LPEP2PEP2\Delta{x}_2 = L* \frac{\sqrt{P_E} - \sqrt{P_2}}{\sqrt{P_E} \sqrt{P_2}}

3.3 Luồng swap hợp lý

Ở hai ví dụ trên chỉ là mình đang minh hoạ cho sự dịch chuyển của price và vận dụng các công thức vào tính toán output. Thực tế, Pool không thể tự xác định trước được từ input của một lệnh swap rằng lệnh swap đó sẽ là swap trong price-range hay swap cross qua các price-range mà sử dụng các bước tính toán cho hợp lý. Đến đây chắc hầu hết mọi người đều suy luận ra được Pool sẽ thực hiện tính toán như thế nào để đảm bảo luồng chạy hợp lý cho cả swap trong price-range và swap cross price-range. Pool sẽ chia lệnh swap ra thành các step, mỗi step đều nằm gọn trong một price-range, các price-range nối tiếp nhau. Các step gồm các bước như sau:

  • Bước 1: Tính toán canFillAmount của current-price-range, sử dụng công thức (3)

canFillAmount=L(PEP0)canFillAmount = L * (\sqrt{P_E} - \sqrt{P_0})

  • Bước 2. So sánh canFillAmountcanFillAmountΔy\Delta{y} để xác định P1P_1 của step này.
    • 2.1: Nếu canFillAmount>Δy=>P1<PEcanFillAmount > \Delta{y} => P_1 < P_E. Nghĩa là toàn bộ lệnh swap sẽ được fill ở step này.

    P1=ΔyL+P0P_1= \frac{\Delta{y}}{L} + P_0

    • 2.2: Nếu canFillAmount<=ΔycanFillAmount <= \Delta{y}. Nghĩa là ở step này chỉ có fill một phần của lệnh swap (canFillAmount<ΔycanFillAmount < \Delta{y}) hoặc vừa khít fill hết (canFillAmount=ΔycanFillAmount = \Delta{y}) và P1=PEP_1 = P_E. Ở đây gom cả <,=<,= vào chung vì theo như bài 1 mình đã viết, thanh khoản của price-range có dạng là [D, E), [E, F), ....
  • Bước 3. Tính filledAmountfilledAmountremainAmountremainAmount

filledAmount=L(P1P0)filledAmount = L * (\sqrt{P_1} - \sqrt{P_0})

remainAmount=ΔyfilledAmountremainAmount = \Delta{y} - filledAmount

  • Bước 4. Tính Δx\Delta{x} của step này

Δx=L(P0P1P0P1)\Delta{x} = L (\frac{\sqrt{P_0} - \sqrt{P_1}}{\sqrt{P_0} \sqrt{P_1}})

  • Bước 5. Xác định step này đã là step dừng hay chưa.
    • 5.1: Nếu remainAmount=0remainAmount = 0 (tương đương với trường hợp 2.1) thì kết thúc
    • 5.2: Nếu remainAmount>0remainAmount > 0 (tương đương vơi trường hợp 2.2) thì chuyển sang step tiếp theo tại price-range E-F. Lúc này P0P_0 của step đó sẽ là PEP_EΔy\Delta{y} của step đó sẽ là remainAmountremainAmount, L của step đó sẽ là L+liquidityNetEL + liquidityNetE (xem lại bài 1)

Vòng lặp này sẽ kết thúc khi remainAmount=0remainAmount = 0 hoặc gặp price-range không có thanh khoản hoặc slippage lớn hơn mức mà Alice chấp nhận. Output mà Alice nhận được là tổng Δx\Delta{x} của tất cả các step.

3.4 Đối chiếu vào code

image.png While loop của hàm swap. Điều kiện dừng là amountRemaining = 0 hoặc price vượt quá price mà user chấp nhận (dòng 641)

Sau đó xác định ranh giới của price-range hiện tại (dòng 646->650), sẽ nói ở bài sau

image.png

Tiếp theo là tính toán các con số của step hiện tại. Cùng xem hàm computeSwapStep(). Để đơn giản thì chúng ta sẽ set với dụ ở trên. Nghĩa là exactIn = truezeroForOne = false

image.png

Bước 1, tính canFillAmountcanFillAmount là dòng 44.

Bước (trường hợp) 2.1 là dòng 45

Bước (trường hợp) 2.2 là dòng 46 -> 52

image.png

Bước 3, tính filledAmountfilledAmount là dòng 78->80

Bước 4, tính Δy\Delta{y} của step này, là dòng 81 -> 83

Quay trở lại với while loop của hàm swap

image.png

Cập nhật remainingremainingΔx\Delta{x} sau step vừa rồi, dòng 674 và 675

image.png Nếu cross-tick thì cập nhật lại L, chuẩn bị cho step tiếp theo.

Oke, sương sương vậy thôi ha, chứ viết kỹ hơn nữa thì mệt quá. Mọi người tự phân tích thêm ha.

Bài sau, mình sẽ viết về Tick Bit Map, tại sao lại cần phải có Tick Bit Map


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí