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:
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à , current-price là .
Ví dụ, Alice đang muốn swap một lượng token1 lấy một lấy token0, chúng ta cần tính xem lượng token0 mà Alice sẽ nhận được là bao nhiêu:
Chúng ta có các tham số đã biết sau:
- => biết được luôn
Các bước tính toán như sau:
- Bước 1: Sử dụng công thức để tính
- Bước 2: Do đã biết được và , dễ dàng suy ra được
- Bước 3: biết được và , thay vào công thức để tính ra được
Nếu để ý, 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 , nghĩa là sẽ chuyển 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.RESOLUTION và FixedPoint96.Q96 mình sẽ nói về chúng ở tập sau:
-
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 token0 lấy token1. Hãy tính toán lượng token1 mà Bob sẽ nhận được:
Chúng ta có các tham số đã biết sau:
- => biết được luôn
Các bước tính toán như sau:
- Bước 1: Sử dụng công thức để tính
- Bước 2: Sử dụng công thức để tính :
Tương tự như ví dụ 1, cũng sẽ ra kết quả âm, điều này thể hiện 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 . 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:
- 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
Alice đang muốn swap một lượng token1 lấy token0. Giả sử:
- current-price đang ở price range D-E
- lượng token0 còn lại trong price-range D-E là chỉ đủ để fill một lượng , và
- 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 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 để hấp thụ hết
Bây giờ sẽ tính các con số 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à
- Bước 1: Tính và . 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 . Sử dụng công thức (4) để tính , như sau
- Bước 2 : Tính . Do chúng ta đã biết chắc rằng toàn bộ phần thừa sẽ bị hấp thu hết trong E-F, nên sẽ nằm trong E-F. Sử dụng công thức (3) để tính
- Bước 3: Tính . Do đã biết . Sử dụng công thức 4 để tính:
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)
- Bước 2. So sánh và để xác định của step này.
- 2.1: Nếu . Nghĩa là toàn bộ lệnh swap sẽ được fill ở step này.
- 2.2: Nếu . Nghĩa là ở step này chỉ có fill một phần của lệnh swap () hoặc vừa khít fill hết () và . Ở đâ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 và
- Bước 4. Tính của step này
- Bước 5. Xác định step này đã là step dừng hay chưa.
- 5.1: Nếu (tương đương với trường hợp 2.1) thì kết thúc
- 5.2: Nếu (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 của step đó sẽ là và của step đó sẽ là , L của step đó sẽ là (xem lại bài 1)
Vòng lặp này sẽ kết thúc khi 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 của tất cả các step.
3.4 Đối chiếu vào code
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
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 = true và zeroForOne = false
Bước 1, tính 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
Bước 3, tính là dòng 78->80
Bước 4, tính của step này, là dòng 81 -> 83
Quay trở lại với while loop của hàm swap
Cập nhật và sau step vừa rồi, dòng 674 và 675
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
Bình luận