+4

Khốn vãi lò. Phần 4: Tick Bit Map

4. Tick Bit Map

Nếu như các bạn để ý và nhớ lại kiến thức ở bài 1, thì ở bước swap, để xác định E là điểm nào không phải chuyện dễ, vì UniswapV3 muốn tạo ra sự linh hoạt cho user nên các price-range không bắt buộc có độ rộng bằng nhau, do đó cần phải có cách để xác định đâu là giới hạn của một price-range nhanh và hiệu quả nhất.

Xét ví dụ như sau:

Đầu tiên là Alice add 60L vào D-E, sau đó Bob add tiếp 60L vào F-G. Nếu Carol add tiếp 60L vào E-F, vậy 3 price-range D-E, E-F, F-G đều có L = 60 thì chúng có hợp nhất thành price-range lớn D-G có L = 60 hay không?

Câu trả lời là không!

Bởi vì, trong trường hợp nếu có 1 lệnh swap nào đó làm dịch chuyển price từ D-E sang E-F và kết thúc, thì lệnh swap đó không được quyền sử dụng liquidity mà Bob đã add vào, cũng như Bob không được hưởng fee từ lệnh swap đó.

Như vậy cần một cách đánh dấu đâu là một điểm dừng của một price-range chứ không thể dùng liquidityNet != 0 để đánh dấu, vì như hình ở trên ta thấy, mặc dù liquidityNet tại E,F sau khi Carol add liquidty là 0, nhưng chúng vẫn là điểm dừng của các price-range, cụ thể là:

  • D,E là giới hạn của price-range D-E
  • E,F là giới hạn của price-range E-F
  • F,G là giới hạn của price-range F-G

4.1 LiquidtyGross và tickInitialized

Ví dụ, Alice thêm 60L vào D-E và Bob thêm 60L vào F-G, lúc đó, liqudityGross và tickInitialized như sau:

Sau đó, Carols thêm 60L và0 E-F:

Như vậy, khi 1 tick có liquidityGross > 0, đồng nghĩa tick đó đang là điểm dừng của một price-range nào đó hoặc có thể nó là điểm dừng của cả 2 price-range lower và upper.

Một tick có liqudityGross > 0 thì tickInitialized của nó phải là true, nếu liquidityGross = 0, thì tickInitalized của nó phải là false => LiquidityGross của 1 tick phải luôn >= 0.

Đối chiếu vào trong code.

image.png

Hàm update() của file Tick.sol. LiquidityGross là kiểu uint không âm và liquidityGrossAfter đơn giản chỉ là =liquidityGrossBefore.addDelta(liquidityDelta).

Ở khối if(liquidityGrossBefore==0) thì info.initialized = true, tương đương là nếu liquidityGrossAfter > 0 thì info.initialized = true. Bởi vì liquidityDelta được truyền vào hàm này luôn khác 0 + liquidityGross phải luôn >= 0.

image.png

Thật vậy, hàm ticks.update() chỉ được gọi ở hàm _updatePosition() của UniswapV3Pool.sol khi liquidityDelta != 0

4.2 Tick bit map

Ở trên chúng ta đã hiểu chức năng của liquidityGross, nó đơn giản chỉ là đánh dấu xem tại tick đó có phải là một ranh giới của price-range nào không.

Tuy nhiên nếu chúng ta sử dụng vòng for để chạy hết tất cả các tick có tickIndex % tickSpacing = 0 để xem tại đó liqudityGross > 0 hay initialized = true hay không để xác định ranh giới sẽ rất tốn gas. Do đó, chúng ta cần một cách tìm nhanh hơn.

Bit map là một kỹ thuật phổ biến để index data bằng cách ngắn gọn

Ví dụ:

Với những tick có liquidityGross > 0 (initialized = true) thi sẽ tương ứng với 1, ngược lại sẽ là 0.

image.png

Ở dòng 130, nếu có sự thay đổi của liquidityGross, thì chúng ta phải lật bit tại tick đó lên:

  • Nếu liquidityGross thay đổi từ 0 thành một số lớn hơn 0, nên được set là 1 tại tick đó
  • Nếu liquidityGross thay đổi từ một số lớn hơn 0 thành 0, nên được set là 0 tại tick đó

Và chúng ta chỉ cần lập bit map cho các tick có tickIndex % tickSpacing = 0. Map sẽ được chia thành các words, mỗi words có 256 bits.

Ví dụ, pool ở trên có tick-spacing là 20 thì bit map cụ thể của nó sẽ như sau:

  • Word 1: chứa bit map của các tick: 5120, 5140, 5160, 5180,...10220.
  • Word 2: chứa bit map của các tick: 10240, 10260, 10280, 10300,...15340.
  • Word 3: chứa bit map của các tick: 15360, 15380, 15400,..., 20460.
  • Word 4: chưa bit map của các tick: 20480,..25580.
  • Word 5: chứa bit map của các tick: 25600,...

Chúng ta cùng nhìn lại vòng while trong hàm swap trong file UniswapV3Pool.sol image.png

hàm này sử dụng hàm nextInitializedTickWithinOneOneWord() để tìm:

  • tick được initialized trong word đó theo chiều swap
  • hoặc tick đầu tiên của word tiếp theo chiều swap

Hàm này trong file TickBitMap.sol: image.png

Về cách hoạt động của hàm này có thể xem thêm tại https://uniswapv3book.com/milestone_2/tick-bitmap-index.html#bitmap

Như vậy, với pool ở trên, nếu có 1 lệnh swap nào đó swap token1 lấy token0, làm giá rơi vào 1 tick nào đó thuộc word 4, thì vòng while sẽ được chạy qua 5 lần như sau:

Mặc dù thực tế chỉ có các price-range là D-E, E-F, F-G, nhưng khi swap sẽ xuẩt hiện thêm các price-range tạm thời của là E-E1, E1-F, F-F1.

Tuy nhiên chúng ta chỉ thực hiện cross-tick ( L = L.addDelta(liquiditiyNet) ) khi thực sự cross qua tick là ranh giới của một price-range thực tế (các tick có bit trong bit map là 1)

image.png

4.4 Swap qua các gaps

Giá sử E-F không có thanh khoảng, 1 lệnh swap có thể xuyên qua E-F và đi thẳng vào F-G hay không?

Câu trả lời là có!

Nhớ lại bài 1, khi price cross qua E, đi vào E-F thì lúc này L sau khi addDelta(liqidityNet_E) chắc chắn phải bằng 0. Chúng ta sẽ có 2 loop ở đây là loop2, loop3 với L = 0 => do đó amountRemainning đi vào rồi đi ra hàm computeSwapStep() sẽ được giữ nguyên và không có lượng output token nào được ghi nhận cho user ở 2 loop này. image.png

Có thể dễ dàng tìm thấy test case của việc này ở file UniswapV3.spec.ts image.png


All rights reserved

Bình luận

Đang tải thêm bình luận...
Avatar
+4
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í