LapTrinhBlockchain

Chia sẻ kiến thức về Lập Trình Blockchain

Lập trình Blockchain, Lập Trình DApp, Lập trình Smart Contract

DFYN V2: Tài liệu phát triển của DFYN V2 – Giúp bạn hiểu hơn mô hình thanh khoản tập trung

DFYN V2: Tài liệu phát triển của DFYN V2 – Kiến thức giúp bạn hiểu hơn về Uniswap V3

DFYN V2: Tài liệu phát triển của DFYN V2 – Kiến thức giúp bạn hiểu hơn về Uniswap V3

Chia sẻ bài viết
5
(88)

Thông qua tài liệu phát triển của Dfyn V2 mà đội dự án chia sẻ khi tổ chức Botathon, mình thấy thông tin khá hay nên mình đã đọc và dịch nó. Khá nhiều kiến thức hay trong đó và qua đó bạn hiểu được về Dfyn V2 và Uniswap V3 (Dfyn V2 khá giống với Uniswap V3, Quickswap V3)

Giới thiệu về Dfyn V2

Về Dfyn V1

Constant Product AMM

Dfyn V1 là một sàn giao dịch phi tập trung AMM (Automated market maker – Nhà tạo lập thị trường tự động) triển khai thuật toán “sản phẩm không đổi” (constant product): xy=k
Đây là một loại AMM duy trì sản phẩm không đổi của nhóm tài sản mà nó nắm giữ. Điều này có nghĩa là tổng giá trị của tài sản trong nhóm không đổi, bất kể giá trị của từng tài sản có thay đổi hay không. Bởi vì chúng duy trì một sản phẩm không đổi, các AMM này có thể mang lại lợi nhuận có thể dự đoán được cho các nhà cung cấp thanh khoản.

Sự kém hiệu quả của Constant Product AMM

Một vấn đề tiềm ẩn với Constant Product AMM là chúng có thể trở nên kém hiệu quả trong một số điều kiện nhất định. Điều này có thể xảy ra khi giá của tài sản trong nhóm thay đổi đáng kể. Nếu giá của các tài sản trong nhóm phân kỳ đáng kể, nhóm có thể trở nên mất cân bằng, với một số tài sản được định giá quá cao và những tài sản khác bị định giá thấp. Điều này có thể dẫn đến sự thiếu hiệu quả trên thị trường và có thể làm giảm sức hấp dẫn của AMM với tư cách là nhà cung cấp thanh khoản. Để giảm thiểu vấn đề này, một số Constant Product AMM có thể sử dụng các cơ chế tái cân bằng để đảm bảo rằng nhóm vẫn cân bằng và hiệu quả.

Trước khi thảo luận về những cải tiến và tính năng mới của Dfyn V2, trước tiên chúng ta hãy xem xét những thiếu sót của Dfyn V1. Bởi vì không phải tất cả các cặp giao dịch đều giống nhau về mức độ biến động giá, nên chúng có thể được nhóm lại cho phù hợp:

  1. Các mã thông báo có mức biến động giá từ trung bình đến cao chiếm phần lớn trong nhóm này. Hầu hết các mã thông báo không có giá cố định với một giá trị cụ thể và do đó có thể biến động theo thị trường. Do đó, nhóm này bao gồm phần lớn các mã thông báo.
  2. Các mã thông báo có độ biến động thấp thuộc nhóm này. Nhóm này chủ yếu bao gồm các token được chốt, chẳng hạn như stablecoin như USDC, USDT và DAI. Nó cũng bao gồm các biến thể của ETH, chẳng hạn như ETH/stETH và ETH/rETH.

Các nhóm này có các yêu cầu thanh khoản khác nhau đối với cấu hình nhóm của chúng. Đối với các mã thông báo ổn định, tính thanh khoản cao là cần thiết để ngăn các giao dịch lớn ảnh hưởng đến giá của mã thông báo. Giá trị của USDC và USDT phải duy trì ổn định, bất kể khối lượng mã thông báo được giao dịch. Thuật toán AMM chung của Dfyn V1 không phù hợp lắm cho giao dịch stablecoin, vì vậy các AMM thay thế như Curve đã trở nên phổ biến hơn cho loại giao dịch này. Vấn đề với Dfyn V1 là tính thanh khoản nhóm của nó được phân phối không giới hạn, cho phép giao dịch ở bất kỳ mức giá nào từ 0 đến vô cùng.

Tính thanh khoản được phân bố không giới hạn trên DFYN V1
Tính thanh khoản được phân bố không giới hạn trên DFYN V1

Tuy nhiên, cách làm này có thể dẫn đến sự kém hiệu quả trong việc sử dụng vốn. Giá lịch sử của một tài sản có xu hướng dao động trong một phạm vi nhất định, cho dù phạm vi đó hẹp hay rộng. Do đó, việc cung cấp thanh khoản trong một vùng giá cách xa mức giá hiện tại hoặc khó có thể đạt được sẽ không tận dụng được vốn hiệu quả.

Nhóm thanh khoản tập trung (Concentrated Liquidity Pool)

Nhóm thanh khoản tập trung là dạng tổng quát của nhóm truyền thống xy = k. Với mô hình truyền thống, tất cả người dùng cung cấp thanh khoản trên phạm vi giá (0, \infty ), còn với trong nhóm thanh khoản tập trung, mỗi người dùng có thể chọn phạm vi của riêng họ để cung cấp thanh khoản. Điều này cho phép người dùng thu hẹp phạm vi cung cấp thanh khoản giúp khuếch đại tính thanh khoản của họ – nghĩa là các nhà giao dịch chịu ít tác động về giá hơn và các nhà cung cấp thanh khoản tích lũy nhiều phí hơn. Điều này cải thiện hiệu quả sử dụng vốn bằng cách cho phép thêm thanh khoản vào một phạm vi giá hẹp, giúp Dfyn trở nên đa dạng hơn: giờ đây nó có thể có các nhóm được định cấu hình cho các cặp có mức độ biến động khác nhau. Đây là cách DfynV2 cải thiện DfynV1.

Tóm lại, một cặp DfynV2 là nhiều cặp DfynV1 nhỏ. Điểm khác biệt chính giữa V1 và V2 là ở V2, có nhiều mức giá trong một cặp. Và mỗi phạm vi giá ngắn hơn này đều có dự trữ hữu hạn. Toàn bộ phạm vi giá từ 0 đến vô hạn được chia thành các phạm vi giá ngắn hơn, với mỗi phạm vi giá có lượng thanh khoản riêng. Tuy nhiên, điều quan trọng là trong phạm vi giá ngắn hơn đó, nó hoạt động chính xác như DfynV1 .

Bây giờ, hãy thử hình dung nó. Điều chúng ta đang nói là chúng ta không muốn đường cong là vô hạn. Chúng ta cắt nó tại các điểm a và b và nói rằng đây là các ranh giới của đường cong. Hơn nữa, chúng ta dịch chuyển đường cong để các ranh giới nằm trên các trục. Đây là những gì chúng ta nhận được:

Trong mỗi vùng giá thì vẫn sử dụng công thức xy=k
Trong mỗi vùng giá thì vẫn sử dụng công thức xy=k

Mua hoặc bán mã thông báo sẽ di chuyển giá dọc theo đường cong. Một phạm vi giá giới hạn sự chuyển động của giá. Khi giá di chuyển đến một trong hai điểm, nhóm sẽ bị cạn kiệt: một trong các mã thông báo dự trữ sẽ bằng 0 và việc mua mã thông báo này sẽ không thể thực hiện được.

Trên biểu đồ trên, giả sử rằng giá khởi điểm nằm ở giữa đường cong. Để đi đến điểm a, chúng ta cần mua tất cả y có sẵn và tối đa hóa x trong phạm vi; để đi đến điểm b, chúng ta cần mua tất cả x có sẵn và tối đa hóa y trong phạm vi. Tại những thời điểm này, chỉ có một mã thông báo trong phạm vi.

Điều gì xảy ra khi phạm vi giá hiện tại bị cạn kiệt trong một giao dịch? Giá trượt vào phạm vi giá tiếp theo. Nếu phạm vi giá tiếp theo không tồn tại, giao dịch sẽ kết thúc một phần được thực hiện-chúng ta sẽ thấy điều này hoạt động như thế nào ở phần sau của cuốn sách.

Đây là cách tính thanh khoản được lan truyền trong nhóm USDC/ETH trên môi trường mainnet:

Thanh khoản tập trung ở gần mức giá hiện tại
Thanh khoản tập trung ở gần mức giá hiện tại

Bạn có thể thấy rằng có rất nhiều thanh khoản xung quanh mức giá hiện tại nhưng càng xa mức giá đó thì càng ít thanh khoản hơn – điều này là do các nhà cung cấp thanh khoản cố gắng sử dụng vốn hiệu quả hơn. Ngoài ra, toàn bộ phạm vi không phải là vô hạn, ranh giới trên của nó được hiển thị trên hình ảnh.

Kiến trúc của Dfyn V2

Dfyn V2 Ticks

Tick là gì?

Trong thị trường tài chính, Tick đề cập đến sự thay đổi nhỏ nhất có thể về giá của một công cụ tài chính. Chẳng hạn, nếu một nội dung cụ thể có kích thước đánh dấu là 0,01 đô la, điều này có nghĩa là giá của nó chỉ có thể thay đổi theo mức tăng 0,01 đô la và không thể dao động với số lượng nhỏ hơn. Kích thước Tick (Tick Size) cho các chứng khoán khác nhau có thể khác nhau tùy thuộc vào các yếu tố khác nhau, chẳng hạn như tính thanh khoản của chứng khoán và biến động giá tối thiểu mà thị trường có thể đáp ứng.

Tick trong Dfyn V2

Dfyn V2 cho phép giao dịch chính xác hơn bằng cách chia phạm vi giá [0,∞] thành các đoạn nhỏ, tương tự như một sàn giao dịch sổ lệnh:

  • Hệ thống xác định phạm vi giá cho mỗi Tick, thay vì dựa vào đầu vào của người dùng.
  • Các giao dịch trong một tích tắc vẫn tuân theo công thức của AMM, nhưng phương trình phải được cập nhật khi giá vượt qua một Tick.
  • Lệnh có thể được thực hiện ở bất kỳ mức giá nào trong phạm vi Tick. Trong trường hợp lệnh giới hạn, chúng ta có “Tick lệnh giới hạn” (limit order ticks) (thảo luận trong phần tiếp theo).
  • Đối với mỗi vị trí mới, chúng ta chèn hai phần tử vào danh sách được liên kết dựa trên giá bắt đầu và kết thúc của phạm vi. Để giữ cho danh sách có thể quản lý được, chúng ta giới hạn giá của phạm vi là lũy thừa của 1,0001.
    Ví dụ: Phạm vi của chúng ta có thể bắt đầu tại tick(0) với mức giá $1,0000 = 1,0001^0 và kết thúc tại tick(23028), tương ứng với mức giá xấp xỉ $10,0010 = 1,0001^23028. Sử dụng phương pháp này, chúng ta có thể bao gồm toàn bộ phạm vi giá (0, ∞) chỉ sử dụng các giá trị số nguyên 24 bit.

Danh sách liên kết được biểu thị bằng ánh xạ các số nguyên 24 bit tới các cấu trúc Tick, trong đó mỗi Tick chứa các con trỏ tới dấu tích trước đó và dấu tích tiếp theo, cũng như tính thanh khoản và các biến khác theo dõi phí và thời gian sử dụng trong một phạm vi.

struct Tick {
    int24 nextTickToCross;
    uint160 secondsGrowthGlobal;
    uint256 currentLiquidity;
    uint256 feeGrowthGlobalA;
    uint256 feeGrowthGlobalB;
    bool zeroForOne;
    uint24 tickSpacing;
}

On-Chain Limit Orders (Các lệnh Limit trên chuỗi)

Tìm hiểu về on-chain limit order

On-chain limit order là các lệnh được đặt trên một chuỗi khối chỉ định mức giá tối đa hoặc tối thiểu mà người dùng sẵn sàng mua hoặc bán một tài sản cụ thể. Các lệnh này được thực hiện tự động khi giá thị trường của tài sản đạt đến mức giá đã chỉ định, cho phép người dùng giao dịch tài sản mà không cần đến bên trung gian thứ ba. Chúng cung cấp một cách thuận tiện để người dùng giao dịch tài sản một cách minh bạch và đáng tin cậy mà không cần phải dựa vào cơ quan trung ương để khớp người mua và người bán.

On-Chain limit Order trong Dfyn V2

Trước đây, logic của tick truyền thống đã đặt ra những hạn chế đối với khả năng phân phối thanh khoản của chúng ta trong một phạm vi. Tuy nhiên, thông qua những cải tiến gần đây, AMM của Dfyn V2 đã giới thiệu “Limit Order Ticks“, cho phép thanh khoản tập trung cao độ trong một Tick đơn, dẫn đến độ chính xác của giá được nâng cao. Điều này cho phép người dùng thêm thanh khoản giới hạn ở một mức giá chính xác, tương tự như cách hoạt động của các sàn giao dịch sổ lệnh.

Các lợi ích từ On-Chain Limit Order

Có một số lợi ích tiềm năng khi sử dụng các lệnh giới hạn trên chuỗi (on-chain limit order):

  • Tự động hóa: Các lệnh giới hạn trên chuỗi được thực hiện tự động khi giá thị trường đạt đến mức giá đã chỉ định, cho phép giao dịch liền mạch và hiệu quả mà không cần bên trung gian thứ ba.
  • Đảm bảo: Với thanh khoản giới hạn của chúng ta được nhúng vào chính đường cong khi giá vượt qua một mức giá nhất định, lệnh giới hạn tại thời điểm đó chắc chắn sẽ được lấp đầy. Nếu không làm cạn kiệt thanh khoản của lệnh giới hạn ở mức giá đó, giá thị trường sẽ không đi xa hơn. Do đó đưa ra một sự đảm bảo về việc thực hiện đơn đặt hàng.
  • Minh bạch: Bởi vì các lệnh giới hạn trên chuỗi được thực hiện trên một chuỗi khối, chúng cung cấp một bản ghi minh bạch và có thể kiểm chứng về tất cả các giao dịch. Điều này có thể giúp xây dựng lòng tin và sự tự tin trên thị trường.
  • Bảo vệ: Các lệnh giới hạn trên chuỗi được hỗ trợ bởi các hợp đồng thông minh, cung cấp một cách thực hiện giao dịch an toàn và chống giả mạo. Điều này làm giảm nguy cơ gian lận và đảm bảo rằng người dùng luôn giữ quyền kiểm soát tài sản của họ.
  • Tiết kiệm chi phí: Các lệnh giới hạn trên chuỗi có thể giúp người dùng tiết kiệm phí giao dịch, vì chúng không yêu cầu dịch vụ của cơ quan trung ương để khớp người mua và người bán.
  • Khả năng tiếp cận: Các lệnh giới hạn trên chuỗi cho phép người dùng giao dịch tài sản trên các sàn giao dịch phi tập trung, có thể được truy cập từ mọi nơi trên thế giới có kết nối internet. Điều này có thể cung cấp quyền truy cập nhiều hơn vào thị trường tài chính toàn cầu cho những người dùng có thể không có quyền truy cập vào các dịch vụ tài chính truyền thống.

Toán học trong Dfyn V2

Về mặt toán học, Dfyn V2 sử dụng các công thức giống như Dfyn V1, nhưng chúng đã được cải tiến và tăng thêm so với V1.

Liquidity Math (Công thức tính toán thanh khoản)

Để tạo điều kiện chuyển đổi giữa các phạm vi giá, hợp lý hóa quản lý thanh khoản và tránh lỗi làm tròn, Dfyn V2 giới thiệu các khái niệm sau:

Công thức tính L - L là Đại diện cho tính thanh khoản

Tính thanh khoản của một nhóm được xác định bằng tích của hai số, đại diện cho lượng dự trữ mã thông báo trong nhóm. Thước đo tính thanh khoản, được đại diện bởi biến L, là căn bậc hai của tích của hai số này (xy). Điều này có nghĩa là Ltrung bình nhân của x và y, và khi nhân L với chính nó, kết quả sẽ bằng tích của x và y (Và nó chính là hằng số k).

Price Math (Công thức tính giá)

P là giá token 0 tính theo token 1

y/x là giá của mã thông báo 0 tính theo mã thông báo 1. Vì giá mã thông báo trong một nhóm là nghịch đảo của nhau, nên chúng ta chỉ có thể sử dụng một trong số chúng trong phép tính (và theo quy ước, DfynV2 sử dụng y/x).

Tại sao chúng ta lại sử dụng sqrt(P) (căn bậc 2 của P) thay vì sử dụng trực tiếp P? Có 2 lý do:

  1. Việc lưu trữ P thì trong quá trình tính toán cần phải tính toán căn bậc hai, đây là phép tính không chính xác và gây ra lỗi làm tròn. Do đó, việc lưu trữ căn bậc hai mà sẽ giúp không cần tính toán trong hợp đồng sẽ dễ dàng hơn (chúng ta sẽ không lưu trữ x và y trong hợp đồng).
  2. Căn bậc 2 của P có mỗi liên hệ thú vị với L: L cũng là mối quan hệ giữa thay đổi về lượng đầu ra và thay đổi về sqrt(P)
Mối liên hệ thú vị giữa L và căn bậc 2 của P

Một lần nữa, chúng ta không cần tính giá thực tế – chúng ta có thể tính toán lượng đầu ra ngay lập tức. Ngoài ra, vì chúng ta sẽ không theo dõi và lưu trữ x và y nên tính toán của chúng ta sẽ chỉ dựa trên L sqrt(P). Từ công thức trên, chúng ta có thể tính được Δy.

Như chúng ta đã thảo luận ở trên, giá trong một nhóm là đối ứng của nhau. Do đó Δx là:

Lsqrt(P) cho phép chúng ta không lưu trữ và cập nhật bất kỳ khoản dự trữ nhóm nào. Ngoài ra, chúng ta không cần tính toán sqrt(P) mỗi lần vì chúng ta luôn có thể tìm thấy Δsqrt(P) và nghịch đảo của nó.

Tick Math (Công thức tính Tick)

Như chúng ta đã biết trong chương này, phạm vi giá vô hạn của V1 được chia thành các phạm vi giá ngắn hơn trong V2. Mỗi phạm vi giá ngắn hơn này được giới hạn bởi ranh giới–điểm trên và điểm dưới. Để theo dõi tọa độ của các ranh giới này, Dfyn V2 sử dụng các Tick.

Trong Dfyn V2, toàn bộ phạm vi giá được phân định bằng các Tick riêng biệt được phân bổ đều. Mỗi Tick có một chỉ số và tương ứng với một mức giá nhất định:

Trong đó p(i) tương ứng với giá tại điểm Tick (i). Việc lấy lũy thừa 1,0001 có một thuộc tính mong muốn: chênh lệch giữa hai dấu tích liền kề là 0,01% hoặc 1 Basis Point (Điểm cơ bản). Basis Point (1/100 của 1%, hoặc 0,01%, hoặc 0,0001) là một đơn vị đo lường tỷ lệ phần trăm trong thị trường tài chính truyền thống.

Như đã thảo luận ở trên, chúng ta không bao giờ chỉ lưu trữ P trong hợp đồng của mình mà chỉ lưu trữ sqrt(P). Như vậy công thức trên trở thành:

Vì vậy, khi chúng ta lặp lại các giá trị của i, chúng ta nhận được giá trị là:

Tick là số nguyên có thể dương và âm và tất nhiên, chúng không phải là vô hạn. Dfyn V2 lưu trữ sqrt(P) dưới dạng số phẩy cố định Q64.96, là số hữu tỉ sử dụng 64 bit cho phần nguyên96 bit cho phần phân số. Do đó, giá (bằng bình phương của sqrt(P) ) nằm trong phạm vi: [2^128, 2^-128]. Theo phép tính này, phạm vi Tick sẽ trở thành:

Phạm vi của Tick

Smart Contracts (Các hợp đồng trong Dfyn V2)

Dưới đây là danh sách các hợp đồng góp phần vận hành trơn tru Dfyn V2

Master Deployer

Hợp đồng có một số chức năng cho phép chủ sở hữu triển khai nhóm, thêm và xóa các nhà máy khỏi danh sách trắng và cập nhật phí. Hợp đồng cũng bao gồm các sự kiện được bắn ra khi thực hiện một số hành động nhất định, chẳng hạn như thêm một nhà máy vào danh sách trắng hoặc triển khai một nhóm.

ConcentratedLiquidityPool

Hợp đồng ConcentratedLiquidityPool là một triển khai của nhóm thanh khoản tập trung cho giao thức Dfyn. Nó cho phép người dùng đúc, đốt và hoán đổi mã thông báo trong nhóm đồng thời theo dõi tọa độ của ranh giới giá trên và dưới bằng cách sử dụng dấu tích. Hợp đồng cũng xử lý việc tạo và quản lý các lệnh giới hạn. Nó nhập một số thư viện để hỗ trợ tính toán và xác thực, cũng như một số giao diện để tương tác với các hợp đồng khác trong giao thức Dfyn.

ConcentratedLiquidityPoolFactory

Hợp đồng ConcentratedLiquidityPoolFactory là một hợp đồng gốc được sử dụng để triển khai các hợp đồng ConcentratedLiquidityPool với các cấu hình cụ thể. Hợp đồng cho phép chủ sở hữu đặt địa chỉ của người triển khai và cập nhật phí hoán đổi cho một nhóm nhất định.

ConcentratedLiquidityPoolHelper

Hợp đồng ConcentratedLiquidityPoolHelper là một hợp đồng ngoại vi để đọc thông tin trạng thái từ nhóm thanh khoản tập trung. Nó cung cấp các chức năng để đọc trạng thái đánh dấu, giá trị cũ thấp hơn và giá trị cũ cao hơn, giá trị giới hạn cũ thấp hơn và giới hạn cũ cao hơn. Hợp đồng này hữu ích để đọc thông tin từ nhóm thanh khoản tập trung mà không cần tương tác trực tiếp với nó.

ConcentratedLiquidityPoolManager

Hợp đồng ConcentratedLiquidityPoolManager là hợp đồng ngoại vi cho nhóm thanh khoản tập trung Dfyn. Nó kết hợp quản lý vị trí không thể thay thế và đặt cược, đồng thời cung cấp các chức năng để đúc và đổi vị trí, tính toán phần thưởng nhóm và cập nhật vị trí đặt cược. Hợp đồng này được sử dụng để quản lý và theo dõi các vị trí trong nhóm thanh khoản tập trung và để tạo điều kiện đặt cược và phần thưởng cho những người tham gia nhóm.

ConcentratedPoolDeployer

Hợp đồng này được sử dụng bởi ConcentratedLiquidityPoolFactory để triển khai các nhóm mới với các cấu hình được chỉ định. Nó cũng trả về hàm băm mã init của pool.

LimitOrderManager

Hợp đồng LimitOrderManager là hợp đồng để quản lý các lệnh giới hạn trên sàn giao dịch Dfyn. Nó có các chức năng tạo, yêu cầu và hủy các lệnh giới hạn. Nó cũng có một sự kiện để phát ra khi một lệnh giới hạn mới được tạo, yêu cầu hoặc hủy bỏ.

Hợp đồng này được sử dụng giao diện IConcentratedLiquidityPool để quản lý các lệnh giới hạn trong nhóm thanh khoản tập trung. Nó sử dụng hợp đồng LimitOrderToken để đúc các mã thông báo không thể thay thế cho mỗi lệnh giới hạn.

LimitOrderToken

Hợp đồng có hai chức năng: mintburn. Hàm mint cho phép chủ sở hữu hợp đồng đúc mã thông báo mới bằng cách chỉ định người nhận và ID mã thông báo. Chức năng burn cho phép chủ sở hữu ghi các mã thông báo hiện có bằng cách chỉ định ID mã thông báo.

Vault

Hợp đồng này cho phép người dùng gửi, rút và chuyển mã thông báo ERC20. Nó cũng cho phép người dùng mượn mã thông báo thông qua cơ chế “flash loan” (cho vay nhanh), cho phép người dùng mượn một lượng mã thông báo nhất định trong một khoảng thời gian ngắn và sau đó trả lại.

Hợp đồng duy trì số dư cho mỗi người dùng đối với mỗi mã thông báo ERC20 mà họ đã gửi và nó sử dụng cơ chế “rebase” để theo dõi tổng nguồn cung của từng mã thông báo. Hợp đồng cũng cho phép chủ sở hữu đưa vào danh sách trắng một số địa chỉ nhất định, điều này cho phép những địa chỉ đó mượn mã thông báo mà không phải trả phí.

Quoter

QuoterV2 cung cấp báo giá cho các giao dịch hoán đổi trên sàn giao dịch phi tập trung. Nó cho phép người dùng nhận được số lượng tài sản dự kiến mà họ sẽ nhận được trong một giao dịch hoán đổi trước khi thực hiện giao dịch hoán đổi. Nó thực hiện điều này bằng cách thực hiện trao đổi một cách lạc quan và kiểm tra số tiền trong cuộc gọi lại. Hợp đồng cũng có chức năng phân tích lý do hoàn nguyên của giao dịch, lý do này phải chứa dữ liệu báo giá, bao gồm số lượng tài sản nhận được, giá của tài sản và số lần đánh dấu trong giao dịch hoán đổi.

MixedQuoter

MixedRouteQuoterV1 là một hợp đồng cho phép bạn nhận được số tiền dự kiến cho một giao dịch hoán đổi nhất định mà không cần thực hiện giao dịch hoán đổi. Nó cung cấp báo giá trên chuỗi cho các giao dịch hoán đổi đầu vào chính xác Dfyn V1.0, Dfyn V2.0 và MixedRoute. Nó thực hiện trao đổi một cách lạc quan và kiểm tra số tiền trong cuộc gọi lại. Hợp đồng này có các chức năng nhận báo giá cho các giao dịch hoán đổi giữa hai mã thông báo bằng cách sử dụng tuyến đường dex độc lập hoặc các tuyến đường hỗn hợp và chức năng để phân tích lý do hoàn nguyên có chứa dấu ngoặc kép số. Nó cũng có chức năng gọi lại hoán đổi được gọi khi thực hiện hoán đổi và cung cấp số tiền cũng như giá sqrt và đánh dấu sau khi hoán đổi.

Dfyn Signal

Efficient Order Routing in Decentralised Exchanges

Định tuyến lệnh hiệu quả (Efficient Order Routing) là một phương pháp được sử dụng trong giao dịch để định tuyến các lệnh tới nhiều sàn giao dịch hoặc nhiều nhóm thanh khoản nhằm tìm giá tốt nhất cho một tài sản cụ thể. Trong một sàn giao dịch phi tập trung (DEX), một bộ định tuyến đặt hàng có thể được sử dụng để định tuyến các đơn đặt hàng đến nhiều nhóm thanh khoản nhằm tìm giá tốt nhất và thực hiện giao dịch.

Bộ định tuyến đặt hàng hiệu quả sử dụng các thuật toán để phân tích dữ liệu thị trường và xác định chiến lược thực hiện tốt nhất cho một đơn đặt hàng cụ thể. Họ có thể xem xét nhiều yếu tố, chẳng hạn như giá thị trường hiện tại, quy mô của lệnh và thời gian còn lại cho đến khi lệnh hết hạn.

Cơ chế Định tuyến Lệnh (Order Routing) thường được các nhà giao dịch chuyên nghiệp và nhà tạo lập thị trường sử dụng để thực hiện các lệnh lớn hoặc giao dịch trên các thị trường biến động nhanh. Chúng có thể giúp giảm tác động của giao dịch lên thị trường và cải thiện khả năng nhận được giá tốt. Tuy nhiên, chúng cũng có thể được các nhà đầu tư bán lẻ sử dụng để thực hiện giao dịch hiệu quả hơn và với mức giá tốt hơn.

Dex Aggregators

Dex Aggregator (Công cụ tổng hợp DEX) cho phép người dùng truy cập và giao dịch trên nhiều sàn giao dịch phi tập trung (DEX) từ một nền tảng duy nhất. Công cụ tổng hợp DEX hoạt động bằng cách kết nối với nhiều DEX và tổng hợp nhóm thanh khoản của chúng, để người dùng có thể tiếp cận nhiều cặp giao dịch hơn và có khả năng nhận được mức giá tốt hơn cho các giao dịch của họ.

Công cụ tổng hợp DEX thường sử dụng thuật toán định tuyến đơn đặt hàng thông minh (smart order routing algorithm) để tìm giá tốt nhất cho một giao dịch cụ thể trên tất cả các DEX được kết nối.

Sử dụng công cụ tổng hợp DEX có thể giúp người dùng truy cập và giao dịch trên nhiều DEX dễ dàng hơn mà không cần phải kết nối ví web3 của họ trên từng nền tảng riêng lẻ. Nó cũng có thể giúp cải thiện tính thanh khoản và hiệu quả của hệ sinh thái trao đổi phi tập trung tổng thể.

Market Maker Aggregator

Công cụ tổng hợp nhà tạo lập thị trường (market maker aggregator) cho phép người dùng truy cập và giao dịch trên nhiều nhóm tạo lập thị trường từ một nền tảng duy nhất. Các nhà tạo lập thị trường là các công ty hoặc cá nhân cung cấp tính thanh khoản cho thị trường bằng cách liên tục mua và bán tài sản. Họ đóng một vai trò quan trọng trong việc duy trì thị trường có trật tự và tạo điều kiện thuận lợi cho các giao dịch.

Công cụ tổng hợp nhà tạo lập thị trường hoạt động bằng cách kết nối với nhiều nhóm tạo lập thị trường và tổng hợp tính thanh khoản của họ, để người dùng có thể tiếp cận nhiều loại cặp giao dịch hơn và có khả năng nhận được mức giá tốt hơn cho các giao dịch của họ.

Bây giờ chúng ta đã xem xét các mô hình giao dịch khác nhau, hãy xem cách chúng ta đã tận dụng tốt nhất mọi mô hình và tạo ra một công cụ giao dịch được tối ưu hóa cao.

Giới thiệu về Dfyn Signal

Với việc phát hành Dfyn V2.0, chúng ta tự hào giới thiệu công cụ giao dịch tiên tiến của mình, công cụ này kết hợp các khả năng của bộ định tuyến đặt lệnh thông minh (smart order router), trình tổng hợp DEX (DEX aggregator) và trình tổng hợp nhà tạo lập thị trường (market maker aggregator) vào một nền tảng duy nhất. Hệ thống sáng tạo này cho phép người dùng truy cập và giao dịch trên nhiều sàn giao dịch và nhóm thanh khoản từ một giao diện duy nhất, sử dụng các thuật toán tinh vi để tìm giá tốt nhất và thực hiện giao dịch hiệu quả.

Công cụ giao dịch của chúng ta mang lại nhiều lợi ích, bao gồm tính thanh khoản cao hơn và dày đặc hơn, nghĩa là có nhiều cơ hội hơn để mua hoặc bán ở một mức giá nhất định. Điều này dẫn đến ít trượt giá hơn hoặc chênh lệch giữa giá dự kiến của một giao dịch và giá thực tế mà giao dịch đó được thực hiện. Bằng cách giảm thiểu trượt giá, hệ thống của chúng ta giúp đảm bảo rằng người dùng nhận được mức giá tốt nhất hiện có trên thị trường.

Ngoài ra, công cụ giao dịch của chúng ta bao gồm một loạt các công cụ và tính năng để hỗ trợ người dùng đưa ra các quyết định giao dịch sáng suốt.

Nhìn chung, công cụ giao dịch của chúng ta đại diện cho một bước tiến lớn trong thế giới tài chính phi tập trung, cung cấp cho người dùng một cách hiệu quả và hiệu quả hơn để giao dịch và tiếp cận thanh khoản.

Để đảm bảo rằng người dùng của chúng ta nhận được báo giá tốt nhất có thể cho các giao dịch của họ, Dfyn Signal sử dụng một hệ thống tinh vi tìm kiếm thông qua nhiều nhóm thanh khoản và nhà tạo lập thị trường trên các giao thức khác nhau. Bằng cách duyệt qua các thực thể khác nhau này, Dfyn Signal có thể lấy các báo giá tốt nhất từ mỗi thực thể và sau đó tổng hợp chúng để tìm ra lộ trình tối ưu nhất cho giao dịch.

Quá trình này giúp đảm bảo rằng người dùng của chúng ta nhận được mức giá tốt nhất hiện có trên thị trường, cũng như tính thanh khoản cao nhất cho các giao dịch của họ. Bằng cách tiếp cận nhiều nhóm thanh khoản và nhà tạo lập thị trường, chúng ta có thể cung cấp cho người dùng nhiều lựa chọn giao dịch hơn, điều này có thể giúp cải thiện hiệu suất và hiệu quả giao dịch của họ.

Ngoài việc lấy báo giá tốt nhất, Dfyn Signal còn bao gồm một loạt các công cụ và tính năng để hỗ trợ người dùng đưa ra các quyết định giao dịch sáng suốt.

Nhìn chung, Dfyn Signal được thiết kế để giúp giao dịch dễ dàng hơn, hiệu quả hơn và mang lại nhiều lợi nhuận hơn cho người dùng của chúng ta bằng cách cung cấp cho họ quyền truy cập vào các báo giá và thanh khoản tốt nhất hiện có trên thị trường.

Chúng ta hãy xem luồng của Dfyn Signal:

  1. Bước 1: Người dùng đến giao diện người dùng Dfyn với một yêu cầu giao dịch.
  2. Bước 2: Giao diện người dùng Dfyn truy vấn Dfyn Signal với tham số của các giao dịch.
  3. Bước 3: Dfyn Signal tìm nạp báo giá từ tất cả các nguồn có liên quan: Dfyn V1 & V2 AMM, Dfyn RFQ và các DEX bên ngoài khác.
  4. Bước 4: Dfyn Signal tính toán mức phân chia giao dịch tốt nhất và gửi lại cho giao diện người dùng.
  5. Bước 5: Nếu người dùng chấp thuận báo giá, giao dịch sẽ được bắt đầu thông qua công cụ giao dịch của Dfyn.

Hướng dẫn cho các nhà phát triển

Tạo một Pool thanh khoản

Quá trình triển khai một pool bắt đầu từ Hợp đồng MasterDeployer

IMasterDeployer.sol

// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity >=0.8.0;

/// @notice Dfyn pool deployer interface.
interface IMasterDeployer {
    function dfynFee() external view returns (uint256);

    function limitOrderFee() external view returns (uint256);

    function dfynFeeTo() external view returns (address);

    function vault() external view returns (address);

    function migrator() external view returns (address);

    function pools(address pool) external view returns (bool);

    function deployPool(address factory, bytes calldata deployData) external returns (address);

    function protocolFee() external view returns (uint256, uint256);
}

Vì chúng ta có thể có nhiều loại nhóm, trong đó mỗi loại có Hợp đồng MasterDeployer duy nhất của riêng nó được sử dụng để triển khai các Pool. Hiện tại, chúng ta sẽ triển khai Nhóm thanh khoản tập trung (Concentrated Liquidity Pool).

function deployPool(address _factory, bytes calldata _deployData) external returns (address pool) {
}

Hàm DeployPool được sử dụng để triển khai một nhóm mới bằng hợp đồng gốc. Hàm nhận hai đối số: một địa chỉ cho hợp đồng Factory sẽ được sử dụng để triển khai nhóm và byte calldata cho dữ liệu triển khai sẽ được chuyển đến hợp đồng Factory.

 if (!whitelistedFactories[_factory]) revert NotWhitelisted();

Trước khi triển khai nhóm, hàm sẽ kiểm tra xem hợp đồng của Factory có nằm trong danh sách trắng hay không bằng cách kiểm tra ánh xạ whitelistedFactories. Nếu hợp đồng của nhà máy không có trong danh sách trắng, thì chức năng này sẽ trở lại với lỗi NotWhitelisted.

pool = IPoolFactory(_factory).deployPool(_deployData);
pools[pool] = true;
emit DeployPool(_factory, pool, _deployData);

Nếu hợp đồng của Factory nằm trong danh sách cho phép, thì hàm này sẽ gọi hàm DeployPool trên hợp đồng của Factory, chuyển dữ liệu triển khai dưới dạng đối số. Sau đó, địa chỉ của nhóm mới được triển khai sẽ được lưu trữ trong ánh xạ nhóm và một sự kiện được phát ra để cho biết rằng một nhóm mới đã được triển khai. Hàm trả về địa chỉ của nhóm mới được triển khai.

ConcentratedLiquidityPoolFactory.sol

Hãy xem chức năng deployPool trên hợp đồng Factory.

function deployPool(bytes memory _deployData) external returns (address pool) {
    address[] memory tokens;

    (address tokenA, address tokenB, uint160 price, uint24 tickSpacing) = abi.decode(_deployData, (address, address, uint160, uint24));

    // Revert instead of switching tokens and inverting price.
    if (tokenA > tokenB) revert WrongTokenOrder();

    // Strips any extra data.
    // Don't include price in _deployData to enable predictable address calculation.
    _deployData = abi.encode(tokenA, tokenB, tickSpacing);
    bytes32 salt = keccak256(abi.encode(tokenA, tokenB));
    tokens = new address[](2);
    tokens[0] = tokenA;
    tokens[1] = tokenB;
    (pool) = ConcentratedPoolDeployer(deployer).deployConcentratedPool(salt);
    _registerPool(pool, tokens, keccak256(_deployData));
    IConcentratedLiquidityPool(pool).initialize(_deployData, masterDeployer, limitOrderManager);
    IConcentratedLiquidityPool(pool).setPrice(price);
    IConcentratedLiquidityPool(pool).updateSwapFee(MIN_SWAP_FEE);
    IConcentratedLiquidityPool(pool).updateProtocolFee();
}

Hàm deployPool được sử dụng để triển khai nhóm thanh khoản tập trung mới bằng cách sử dụng hợp đồng ConcentratedPoolDeployer. Hàm nhận một đối số: bytes memory cho dữ liệu triển khai sẽ được chuyển đến hợp đồng ConcentratedPoolDeployer.

Hàm bắt đầu bằng cách giải mã dữ liệu triển khai bằng cách sử dụng hàm abi.decode. Dữ liệu triển khai phải chứa địa chỉ của hai mã thông báo, giá và khoảng cách Tick. Sau đó, hàm sẽ kiểm tra xem các mã thông báo có theo đúng thứ tự hay không và nếu không, hàm sẽ hoàn nguyên với lỗi WrongTokenOrder.

Tiếp theo, hàm loại bỏ mọi dữ liệu bổ sung khỏi dữ liệu triển khai và tính toán giá trị salt bằng cách gọi hàm keccak256 trên hai địa chỉ mã thông báo. Sau đó, chức năng triển khai một nhóm thanh khoản tập trung mới bằng cách sử dụng hợp đồng ConcentratedPoolDeployer, chuyển vào giá trị salt làm đối số. Địa chỉ của nhóm mới được triển khai được lưu trữ trong biến pool.

Sau đó, hàm đăng ký nhóm mới bằng cách gọi hàm _registerPool, chuyển vào địa chỉ nhóm, một mảng gồm hai địa chỉ mã thông báo và hàm băm keccak256 của dữ liệu triển khai. Sau đó, hàm khởi tạo nhóm bằng cách gọi hàm initialize trên hợp đồng nhóm, chuyển dữ liệu triển khai, địa chỉ của masterDeployer và địa chỉ của limitOrderManager. Sau đó, hàm này đặt giá của nhóm bằng cách gọi hàm setPrice và đặt phí hoán đổi bằng cách gọi hàm updateSwapFee. Cuối cùng, hàm cập nhật phí giao thức bằng cách gọi hàm updateProtocolFee. Hàm trả về địa chỉ của nhóm mới được triển khai.

ConcentratedPoolDeployer.sol

Hãy xem xét chức năng deployConcentratedPool được gọi bởi CLPFactory.


function deployConcentratedPool(bytes32 salt) external returns (address pool) {
    factory = msg.sender;
    pool = address(new ConcentratedLiquidityPool{salt: salt}());
}

Hàm deployConcentratedPool được sử dụng để triển khai hợp đồng nhóm thanh khoản tập trung mới. Hàm nhận một đối số: bytes32 cho giá trị salt sẽ được sử dụng để tạo một địa chỉ duy nhất cho hợp đồng nhóm.

Hàm bắt đầu bằng cách lưu trữ địa chỉ của người gọi trong biến factory. Đây sẽ là địa chỉ của hợp đồng nhà máy đang gọi chức năng này. Sau đó, hàm tạo một phiên bản mới của hợp đồng ConcentratedLiquidityPool, chuyển giá trị salt làm đối số cho hàm tạo của hợp đồng. Địa chỉ của hợp đồng mới được triển khai được lưu trữ trong biến pool và hàm trả về giá trị này.

Thêm thanh khoản

Việc Thêm/Rút thanh khoản được xử lý bởi ConcentratedLiquidityPoolMananger.sol bằng các chức năng đúc và đốt tương ứng. ConcentratedLiquidityPoolMananger là hợp đồng ngoại vi Nhóm thanh khoản tập trung (Concentrated Liquidity Pool) của Dfyn kết hợp quản lý vị trí không thể thay thế và đặt cược.
Trước tiên hãy xem chức năng đúc

ConcentratedLiquidityPoolMananger.sol

Trong khi thêm thanh khoản, hàm Mint trong ConcentratedLiquidityPoolManager được gọi để tạo vị trí mới dưới dạng NFT được trả về dưới dạng positionId.

Hàm mint được sử dụng để đúc một mã thông báo không thể thay thế (NFT) mới hoặc thêm tính thanh khoản vào một mã hiện có. NFT được liên kết với Nhóm thanh khoản tập trung và giá trị của nó được xác định bởi lượng thanh khoản mà nó nắm giữ và phạm vi giá mà nó bao trùm.

Hàm mint():

/// @notice Mints Position
/// @param pool address of pool
/// @param lowerOld tick below lower tick
/// @param lower lower tick
/// @param upperOld tick below upper
/// @param upper upper tick
/// @param amount0Desired token0 input amount
/// @param amount1Desired token1 input amount
/// @param native user wallet/vault
/// @param minLiquidity min liquidity to mint
/// @param positionId position nft id

function mint(
    IConcentratedLiquidityPool pool,
    int24 lowerOld,
    int24 lower,
    int24 upperOld,
    int24 upper,
    uint128 amount0Desired,
    uint128 amount1Desired,
    bool native,
    uint256 minLiquidity,
    uint256 positionId
) external payable returns (uint256 _positionId) {
    require(masterDeployer.pools(address(pool)), "INVALID_POOL");
    cachedMsgSender = msg.sender;
    cachedPool = address(pool);
    uint128 liquidityMinted = uint128(
        pool.mint(
            IConcentratedLiquidityPoolStruct.MintParams({
                lowerOld: lowerOld,
                lower: lower,
                upperOld: upperOld,
                upper: upper,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                native: native
            })
        )
    );

    require(liquidityMinted >= minLiquidity, "TOO_LITTLE_RECEIVED");
    (uint256 feeGrowthInside0, uint256 feeGrowthInside1) = pool.rangeFeeGrowth(lower, upper);

    if (positionId == 0) {
        // We mint a new NFT.
        _positionId = nftCount.minted;
        positions[_positionId] = Position({
            pool: pool,
            liquidity: liquidityMinted,
            lower: lower,
            upper: upper,
            latestAddition: uint32(block.timestamp),
            feeGrowthInside0: feeGrowthInside0,
            feeGrowthInside1: feeGrowthInside1
        });
        mint(msg.sender);
    } else {
        // We increase liquidity for an existing NFT.
        _positionId = positionId;
        Position storage position = positions[_positionId];
        require(address(position.pool) == address(pool), "POOL_MIS_MATCH");
        require(position.lower == lower && position.upper == upper, "RANGE_MIS_MATCH");
        require(ownerOf(positionId) == msg.sender, "NOT_ID_OWNER");
        // Fees should be claimed first.
        position.feeGrowthInside0 = feeGrowthInside0;
        position.feeGrowthInside1 = feeGrowthInside1;
        position.liquidity += liquidityMinted;
        // Incentives should be claimed first.
        position.latestAddition = uint32(block.timestamp);
    }

    emit IncreaseLiquidity(address(pool), msg.sender, _positionId, liquidityMinted);

    cachedMsgSender = address(1);
    cachedPool = address(1);
}

Hàm nhận một số tham số:

  • pool là địa chỉ của Nhóm thanh khoản tập trung mà NFT được liên kết.
  • lowerOld, lower, upperOldupper là các Tick xác định phạm vi giá bao trùm bởi NFT.
  • amount0Desiredamount1Desired là số lượng của hai token (token0 và token1) mà người dùng muốn thêm vào NFT.
  • native là một giá trị boolean cho biết liệu ví/kho tiền của người dùng có phải là mã thông báo gốc hay không.
  • minLiquidity là lượng thanh khoản tối thiểu phải được thêm vào NFT để đúc nó.
  • positionId là ID của NFT, nếu nó đã tồn tại. Nếu nó được đặt thành 0, một NFT mới sẽ được đúc.

Trước tiên, mã kiểm tra xem địa chỉ của nhóm thanh khoản được cung cấp có trong ánh xạ nhóm của hợp đồng masterDeployer hay không. Nếu không, việc thực thi chức năng được hoàn nguyên với thông báo lỗi “INVALID_POOL“. Sau đó, mã lưu địa chỉ của người gọi trong biến cachedMsgSender và địa chỉ của nhóm trong biến cachedPool.

Tiếp đó, mã này gọi hàm mint trên nhóm thanh khoản được cung cấp, chuyển vào cấu trúc MintParams với các giá trị đầu vào của hàm mint làm các trường của nó. Chức năng mint này trên nhóm thanh khoản chịu trách nhiệm tạo mã thông báo thanh khoản mới và trả lại số lượng mã thông báo được đúc.

Sau đó, mã sẽ kiểm tra xem số lượng mã thông báo được đúc có lớn hơn hoặc bằng giá trị minLiquidity được cung cấp làm đầu vào cho hàm mint hay không. Nếu không, việc thực thi chức năng sẽ được hoàn nguyên với thông báo lỗi “TOO_LITTLE_RECEIVED“.

Sau đó, hàm này gọi hàm rangeFeeGrowth trên nhóm thanh khoản được cung cấp, chuyển các giá trị lowerupper làm đầu vào. Hàm rangeFeeGrowth này trả về một bộ gồm hai giá trị uint256 biểu thị mức tăng phí trong phạm vi được xác định bởi các giá trị lowerupper. Bộ trả về sau đó được gán cho các biến feeGrowthInside0feeGrowthInside1.

Các biến này sau đó được sử dụng để cập nhật các trường feeGrowthInside0feeGrowthInside1 của cấu trúc Position, đại diện cho một vị trí trong nhóm thanh khoản. Các trường feeGrowthInside0feeGrowthInside1 của cấu trúc Position được sử dụng để theo dõi các khoản phí đã được tích lũy trên vị trí.

Tham số positionId được kiểm tra xem nó có bằng 0 hay không. Nếu đúng như vậy, một NFT mới sẽ được tạo. Biến _positionId được đặt thành giá trị hiện tại của nftCount.minted, đây có thể là bộ đếm số lượng NFT đã được đúc. Sau đó, một Cấu trúc vị trí mới được tạo và thêm vào ánh xạ vị trí bằng cách sử dụng _positionId làm khóa. Cấu trúc Position lưu trữ thông tin về NFT, bao gồm địa chỉ của nhóm thanh khoản nơi NFT được đúc, lượng thanh khoản được đúc, phạm vi của nhóm thanh khoản, thời điểm NFT được thêm vào lần cuối và phíGrowthInside0 và giá trị feeGrowthInside1. Cuối cùng là chức năng mint được gọi và thông qua địa chỉ của người gọi.

Nếu positionId không phải là 0, cho biết đây là một vị trí NFT hiện có đang được thêm vào, trước tiên, hàm sẽ kiểm tra xem địa chỉ nhóm được chỉ định có khớp với địa chỉ nhóm được lưu trữ trong Cấu trúc vị trí cho NFT đã cho hay không và các giới hạn dưới và giới hạn trên có khớp với các địa chỉ được lưu trữ trong cấu trúc. Sau đó, nó kiểm tra xem người gọi hàm có phải là chủ sở hữu của NFT hay không. Nếu tất cả các bước kiểm tra này đều vượt qua, chức năng này sẽ cập nhật Cấu trúc vị trí với tính thanh khoản mới được thêm vào nhóm, tốc độ tăng phí mới và dấu thời gian của lần bổ sung mới nhất.

Sau khi khai thác hoặc tăng tính thanh khoản, hàm sẽ phát ra một sự kiện có tên là IncreaseLiquidity với thông tin về giao dịch, sau đó đặt lại các biến cục bộ vì lý do bảo mật.

ConcentratedLiquidityPool.sol

Hãy xem chức năng mint của CLP trả về thanh khoản được đúc. Chúng ta tính toán tính thanh khoản do thư viện DyDxMath tạo ra. Hàm Mint thay đổi trạng thái của tick bằng cách chèn tick mới vào danh sách liên kết.

Hàm mint():

/// @dev Mints LP tokens - should be called via the CL pool manager contract.
function mint(MintParams memory mintParams) public lock returns (uint256 liquidityMinted) {
    Validators._ensureTickSpacing(mintParams.lower, mintParams.upper, tickSpacing);
    uint256 priceLower = uint256(TickMath.getSqrtRatioAtTick(mintParams.lower));
    uint256 priceUpper = uint256(TickMath.getSqrtRatioAtTick(mintParams.upper));
    uint256 currentPrice = uint256(price);

    liquidityMinted = DyDxMath.getLiquidityForAmounts(
        priceLower,
        priceUpper,
        currentPrice,
        uint256(mintParams.amount1Desired),
        uint256(mintParams.amount0Desired)
    );

    // Ensure no overflow happens when we cast from uint256 to int128.
    if (liquidityMinted > uint128(type(int128).max)) revert Overflow();

    _updateSecondsPerLiquidity(uint256(liquidity));

    unchecked {
        (uint256 amount0Fees, uint256 amount1Fees) = _updatePosition(
            msg.sender,
            mintParams.lower,
            mintParams.upper,
            int128(uint128(liquidityMinted))
        );
        if (amount0Fees > 0) {
            _transfer(token0, amount0Fees, msg.sender, false);
            reserve0 -= uint128(amount0Fees);
        }
        if (amount1Fees > 0) {
            _transfer(token1, amount1Fees, msg.sender, false);
            reserve1 -= uint128(amount1Fees);
        }

        if (priceLower <= currentPrice && currentPrice < priceUpper) liquidity += uint128(liquidityMinted);
    }

    InsertTickParams memory data = InsertTickParams({
        nearestTick: nearestTick,
        currentPrice: uint160(currentPrice),
        tickCount: tickCount,
        amount: uint128(liquidityMinted)
    });

    (nearestTick, tickCount) = Ticks.insert(
        ticks,
        limitOrderTicks,
        feeGrowthGlobal0,
        feeGrowthGlobal1,
        secondsGrowthGlobal,
        mintParams.lowerOld,
        mintParams.lower,
        mintParams.upperOld,
        mintParams.upper,
        data
    );

    (uint128 amount0Actual, uint128 amount1Actual) = DyDxMath.getAmountsForLiquidity(
        priceLower,
        priceUpper,
        currentPrice,
        liquidityMinted,
        true
    );

    IPositionManager(msg.sender).mintCallback(token0, token1, amount0Actual, amount1Actual, mintParams.native);

    if (amount0Actual != 0) {
        reserve0 += amount0Actual;
        if (reserve0 + limitOrderReserve0 > _balance(token0)) revert Token0Missing();
    }

    if (amount1Actual != 0) {
        reserve1 += amount1Actual;
        if (reserve1 + limitOrderReserve1 > _balance(token1)) revert Token1Missing();
    }

    emit Mint(msg.sender, amount0Actual, amount1Actual, mintParams.lower, mintParams.upper);
}

Mã này xác định chức năng mint trong hợp đồng Nhóm thanh khoản tập trung. Chức năng này được sử dụng để đúc mã thông báo nhóm thanh khoản (LP) mới, đại diện cho phần chia sẻ thanh khoản của người dùng trong nhóm.

Hàm lấy cấu trúc MintParams làm đầu vào, chứa các trường sau:

  • lowerOldupperOld: các dấu tích ngay bên dưới phạm vi giá được bao phủ bởi mã thông báo LP.
  • lowerupper: các dấu tích ngay trên phạm vi giá được bao phủ bởi mã thông báo LP.
  • amount1Desired: số lượng token1 mà người dùng muốn thêm vào nhóm.
  • amount0Desired: số lượng token0 mà người dùng muốn thêm vào nhóm.
  • native: một giá trị boolean cho biết ví/kho tiền của người dùng có phải là mã thông báo gốc hay không.

Hàm Validators._ensureTickSpacing() đang được sử dụng để đảm bảo rằng các giá trị mintParams.lowermintParams.upper là hợp lệ và đáp ứng các tiêu chí về khoảng cách dấu tick.

Sau đó, chúng tôi tìm nạp priceLowerpriceUpper bằng cách sử dụng getSqrtRatioAtTick. Ở đây, chúng tôi tính toán tính thanh khoản sẽ được đúc dựa trên một loạt giá (priceLower và priceUpper), giá hiện tại (currentPrice) và số lượng mong muốn của hai nội dung (mintParams.amount1Desired và mintParams.amount0Desired) bằng cách sử dụng hàm getLiquidityForAmounts của thư viện DyDxMath.

Tiếp đó, chúng tôi kiểm tra xem lượng thanh khoản được đúc (liquidityMinted) có lớn hơn giá trị tối đa có thể được lưu trữ trong loại dữ liệu int128 hay không. Nếu đúng như vậy, mã sẽ trở lại với lỗi Overflow.

Sau bước kiểm tra này, mã gọi hàm _updateSecondsPerLiquidity và chuyển vào liquidity để cập nhật secondsGrowthGlobal. Tại đây, chúng tôi cập nhật các vị trí bằng cách gọi hàm _updatePosition và chuyển vào mintParams.lower, mintParams.upperliquidityMinted. Hàm này trả về một bộ có số tiền phí cho token0 và token1, được lưu trữ trong các biến amount0Feesamount1Fees. Nếu một trong hai số tiền này lớn hơn 0, mã sẽ chuyển số lượng mã thông báo thích hợp vào tài khoản của người gửi, sau đó trừ số tiền đã chuyển từ khoản dự trữ tương ứng.

Cuối cùng, nếu currentPrice nằm trong phạm vi đã chỉ định (priceLowerpriceUpper), thì biến liquidity được tăng theo liquidityMinted. Sau đó, chúng tôi tạo một cấu trúc InsertTickParams với các giá trị Tick, currentPrice, tickCount vàmount gần nhất rồi chuyển cấu trúc này dưới dạng dữ liệu cho Ticks.insertalong cùng với các tham số khác của hàm insert.

Sau đó, chúng tôi gọi hàm DyDxMath.getAmountsForLiquidity và chuyển vào priceLower, priceUpper, currentPrice, liquidityMinted, và các giá trị true. Hàm này tính toán số lượng mã thông báo sẽ được đúc cho một phạm vi giá và thanh khoản nhất định. Hàm trả về một bộ chứa số lượng mã thông báo được tính cho mỗi mã thông báo, được lưu trữ trong các biến amount0Actualamount1Actual. Sau đó, nó gọi hàm mintCallback của hợp đồng người quản lý vị trí để cập nhật vị trí của người dùng.

Sau đó, chúng tôi kiểm tra xem các biến amount0Actualamount1Actual có khác 0 hay không và nếu đúng như vậy, hãy tăng reserve tương ứng theo số tiền đã tính. Tiếp đó, chúng tôi kiểm tra xem tổng của reservelimitOrderReserve tương ứng có lớn hơn số dư của mã thông báo tương ứng hay không. Nếu trường hợp này xảy ra, mã sẽ trở lại với lỗi Token0Missing hoặc Token1Missing, tùy thuộc vào mã thông báo nào bị thiếu.

Cuối cùng, nó bắn ra sự kiện Mint để cho biết rằng mã thông báo LP mới đã được đúc.

Ticks.sol

Dòng tiếp theo là chức năng chèn từ thư viện Tick. Các câu lệnh yêu cầu đảm bảo rằng các dấu tích được đặt trong phạm vi chính xác, ở đâu,
MIN_TICK = -887272MAX_TICK = 887272

Hàm insert():

function insert(
        mapping(int24 => IConcentratedLiquidityPoolStruct.Tick) storage ticks,
        mapping(int24 => ILimitOrderStruct.LimitOrderTickData) storage limitOrderTicks,
        uint256 feeGrowthGlobal0,
        uint256 feeGrowthGlobal1,
        uint160 secondsGrowthGlobal,
        int24 lowerOld,
        int24 lower,
        int24 upperOld,
        int24 upper,
        IConcentratedLiquidityPoolStruct.InsertTickParams memory data
    ) public returns (int24, uint256) {
    require(lower < upper, "WRONG_ORDER");
    require(TickMath.MIN_TICK <= lower, "LOWER_RANGE");
    require(upper <= TickMath.MAX_TICK, "UPPER_RANGE");

    uint128 currentLowerLiquidity = ticks[lower].liquidity;

    if (currentLowerLiquidity != 0 || lower == TickMath.MIN_TICK || limitOrderTicks[lower].isActive) {
        // We are adding liquidity to an existing tick.
        ticks[lower].liquidity = currentLowerLiquidity + data.amount;
        if (currentLowerLiquidity == 0 || lower != TickMath.MIN_TICK) {
            if (lower <= data.nearestTick) {
                ticks[lower].feeGrowthOutside0 = feeGrowthGlobal0;
                ticks[lower].feeGrowthOutside1 = feeGrowthGlobal1;
                ticks[lower].secondsGrowthOutside = secondsGrowthGlobal;
            }
        }
    } else {
        // We are inserting a new tick.
        IConcentratedLiquidityPoolStruct.Tick storage old = ticks[lowerOld];
        int24 oldNextTick = old.nextTick;

        require(
            (old.liquidity != 0 || lowerOld == TickMath.MIN_TICK || limitOrderTicks[lowerOld].isActive) &&
                lowerOld < lower &&
                lower < oldNextTick,
            "LOWER_ORDER"
        );

        if (lower <= data.nearestTick) {
            ticks[lower] = IConcentratedLiquidityPoolStruct.Tick(
                lowerOld,
                oldNextTick,
                data.amount,
                feeGrowthGlobal0,
                feeGrowthGlobal1,
                secondsGrowthGlobal
            );
        } else {
            ticks[lower] = IConcentratedLiquidityPoolStruct.Tick(lowerOld, oldNextTick, data.amount, 0, 0, 0);
        }

        old.nextTick = lower;
        ticks[oldNextTick].previousTick = lower;
        unchecked {
            data.tickCount++;
        }
    }

	
    uint128 currentUpperLiquidity = ticks[upper].liquidity;
    if (currentUpperLiquidity != 0 || upper == TickMath.MAX_TICK || limitOrderTicks[upper].isActive) {
        // We are adding liquidity to an existing tick.
        ticks[upper].liquidity = currentUpperLiquidity + data.amount;
        if (currentUpperLiquidity == 0 || upper != TickMath.MAX_TICK) {
            if (upper <= data.nearestTick) {
                ticks[upper].feeGrowthOutside0 = feeGrowthGlobal0;
                ticks[upper].feeGrowthOutside1 = feeGrowthGlobal1;
                ticks[upper].secondsGrowthOutside = secondsGrowthGlobal;
            }
        }
    } else {
        // Inserting a new tick.
        IConcentratedLiquidityPoolStruct.Tick storage old = ticks[upperOld];
        int24 oldNextTick = old.nextTick;

        require(
            (old.liquidity != 0 || limitOrderTicks[upperOld].isActive || upperOld == TickMath.MAX_TICK) &&
            oldNextTick > upper &&
            upperOld < upper,
            "UPPER_ORDER"
        );

        if (upper <= data.nearestTick) {
            ticks[upper] = IConcentratedLiquidityPoolStruct.Tick(
                upperOld,
                oldNextTick,
                data.amount,
                feeGrowthGlobal0,
                feeGrowthGlobal1,
                secondsGrowthGlobal
            );
        } else {
            ticks[upper] = IConcentratedLiquidityPoolStruct.Tick(upperOld, oldNextTick, data.amount, 0, 0, 0);
        }
        old.nextTick = upper;
        ticks[oldNextTick].previousTick = upper;
        unchecked {
            data.tickCount++;
        }
    }

    int24 tickAtPrice = TickMath.getTickAtSqrtRatio(data.currentPrice);

    if (data.nearestTick < upper && upper <= tickAtPrice) {
        unchecked {
            data.nearestTick = upper;
        }
    } else if (data.nearestTick < lower && lower <= tickAtPrice) {
        unchecked {
            data.nearestTick = lower;
        }
    }

    return (data.nearestTick, data.tickCount);
}

Chức năng này dường như được sử dụng để chèn dữ liệu vào hai ánh xạ:
tickslimitOrderTicks. lowerOld, lower, upperOldupper
các tham số được sử dụng để chỉ định phạm vi tick để chèn dữ liệu vào. Tham số data được truyền với cấu trúc InsertTickParams.

Các câu lệnh require này được sử dụng để đảm bảo rằng đầu vào hợp lệ. Câu lệnh require đầu tiên kiểm tra xem lower có nhỏ hơn upper không. Điều này là cần thiết vì dữ liệu đầu vào phải được chèn vào một phạm vi dấu tích và phạm vi này phải được xác định rõ (nghĩa là nó phải có giới hạn dưới và giới hạn trên). Nếu lower không nhỏ hơn upper, điều đó có nghĩa là phạm vi không được xác định rõ và thông báo lỗi được đưa ra.

Câu lệnh require thứ hai và thứ ba kiểm tra xem lowerupper có nằm trong phạm vi đánh dấu tối thiểu và tối đa tương ứng hay không. Điều này là cần thiết vì dữ liệu đầu vào chỉ có thể được chèn vào các dấu tích trong phạm vi này. Nếu lower hoặc upper nằm ngoài phạm vi này, một thông báo lỗi sẽ được đưa ra.

Sau đó, chúng tôi cập nhật trường liquidity của ánh xạ tick[lower]. Biến currentLowerLiquidity được sử dụng để lưu trữ thanh khoản hiện tại ở tick lower.

Ở đây chúng tôi đang thêm tính thanh khoản vào một đánh dấu hiện có. Nó thực hiện điều này bằng cách kiểm tra xem tính thanh khoản hiện tại ở dấu kiểm lower có khác không hoặc lower có bằng với giá trị đánh dấu tối thiểu hay không hoặc trường isActive của Ánh xạ limitOrderTicks[lower]true. Nếu bất kỳ điều kiện nào trong số này là đúng, chúng tôi sẽ thêm trường amount của cấu trúc data vào tính thanh khoản hiện tại ở dấu kiểm lower.

Ngoài ra, nếu tính thanh khoản hiện tại tại dấu kiểm lower bằng 0 và lower không bằng giá trị đánh dấu tối thiểu, chúng tôi sẽ đặt feeGrowthOutside0, feeGrowthOutside1 Các trườngsecondsGrowthOutside của tick[lower] ánh xạ tới feeGrowthGlobal0, feeGrowthGlobal1secondsGrowthGlobal các giá trị tương ứng. Điều này chỉ được thực hiện nếu lower nhỏ hơn hoặc bằng trường nearestTick của cấu trúc data.

Sau đó, chúng tôi sẽ chèn một dấu kiểm mới vào ánh xạ ticks. Nó được thực hiện bằng cách trước tiên kiểm tra xem tính thanh khoản hiện tại ở dấu kiểm lowerOld có khác không hoặc lowerOld bằng với giá trị đánh dấu tối thiểu hay trường isActive của ánh xạ limitOrderTicks[lowerOld]truelowerOld nhỏ hơn lowerlower nhỏ hơn hơn dấu tích tiếp theo sau lowerOld. Nếu bất kỳ điều kiện nào trong số này không được đáp ứng, một thông báo lỗi sẽ được đưa ra.

Nếu lower nhỏ hơn hoặc bằng trường nearestTick của cấu trúc data, thì chúng ta tạo một cấu trúc Tick mới với lowerOld, đánh dấu tiếp theo saulowerOld, amount, feeGrowthGlobal0, feeGrowthGlobal1secondsGrowthGlobal các trường và chèn nó vào ánh xạ ticks[lower]. Ngược lại, chúng ta tạo một cấu trúc Tick mới với các trường lowerOld, đánh dấu tiếp theo sau lowerOld, và amount, đồng thời đặt giá trị các trường feeGrowthOutside0, feeGrowthOutside1 secondsGrowthOutside về 0.

Cuối cùng, chúng tôi cập nhật trường nextTick của đánh dấu old và trường previousTick của đánh dấu tiếp theo sau old và tăng trường tickCount của cấu trúc data. Điều này được sử dụng để duy trì danh sách đánh dấu được liên kết.

Quy trình tương tự được thực hiện cho tick Upper.

Biến tickAtPrice được tính bằng cách sử dụng phương thức getTickAtSqrtRatio từ TickMath. Chúng tôi kiểm tra xem nearestTick có nhỏ hơn giá trị trên hoặc dưới hay không và nếu có, đặt giá trị nearestTick thành giá trị trên hoặc dưới tương ứng. Cuối cùng, chúng tôi trả về một bộ chứa các giá trị nearestTick tickCount được cập nhật.

ConcentratedLiquidityPoolManager.sol

Hàm mintCallBack():

function mintCallback(
        address token0,
        address token1,
        uint256 amount0,
        uint256 amount1,
        bool native
    ) external override {
    require(msg.sender == cachedPool, "UNAUTHORIZED_CALLBACK");
    if (native) {
        _depositFromUserToVault(token0, cachedMsgSender, msg.sender, amount0);
        _depositFromUserToVault(token1, cachedMsgSender, msg.sender, amount1);
    } else {
        vault.transfer(token0, cachedMsgSender, msg.sender, amount0);
        vault.transfer(token1, cachedMsgSender, msg.sender, amount1);
    }
    cachedMsgSender = address(1);
    cachedPool = address(1);
}

Chức năng gọi lại được kích hoạt khi thao tác đúc tiền được thực hiện trên mã thông báo.

Đầu tiên, hàm kiểm tra xem người gọi hàm có phải là địa chỉ cachedPool dự kiến hay không và đưa ra lỗi nếu không. Nếu tham số nativetrue, thì hàm gọi phương thức _depositFromUserToVault cho cả mã thông báo token0 token1, chuyển số lượng tương ứng là số amount0 amount1. Ngược lại, chức năng gọi chuyển
phương thức của đối tượng vault để chuyển mã thông báo token0 token1 từ địa chỉ cachedMsgSender sang địa chỉ cachedPool. Cuối cùng, hàm đặt lại các giá trị cachedMsgSendercachedPool về địa chỉ 1.

Tạo một Limit Order

Quá trình đặt Limit Order bắt đầu từ hợp đồng LimitOrderManager trong hàm createLimitOrder() được gọi.

LimitOrderManager.sol

Hàm createLimitOrder():

/// @notice Function to create limitorder
/// @param pool pool address
/// @param tick limitorder tick
/// @param lowerOld tick below limitorder tick
/// @param upperOld tick above limitorder tick
/// @param native user wallet/vault
/// @param amountIn amount in
/// @param zeroForOne direction of limitorder
/// @return limitOrder Id
function createLimitOrder(
    IConcentratedLiquidityPool pool,
    int24 tick,
    int24 lowerOld,
    int24 upperOld,
    bool native,
    uint256 amountIn,
    bool zeroForOne
) external payable returns (uint256) {
    require(masterDeployer.pools(address(pool)), "INVALID_POOL");
    require(amountIn != 0, "Amount:Zero");
    cachedPool = address(pool);
    cachedMsgSender = msg.sender;

    (uint160 currentPoolPrice, ) = pool.getPriceAndNearestTicks();
    uint160 sqrtpriceX96 = TickMath.getSqrtRatioAtTick(tick);

    pool.createLimitOrder(tick, lowerOld, upperOld, amountIn, native, zeroForOne);

    uint256 amountOut;
    if (zeroForOne) {
        require(sqrtpriceX96 > currentPoolPrice, "ISO");
        // amountOut = (amountIn * price) / 10**12;
        // amountOut = FullMath.mulDiv(amountIn, price, 10**12);
        amountOut = FullMath.mulDiv(amountIn, sqrtpriceX96, 2**96);
        amountOut = FullMath.mulDiv(amountOut, sqrtpriceX96, 2**96);
    } else {
        require(sqrtpriceX96 < currentPoolPrice, "IBO");
        // amountOut = (amountIn * 10**12) / price;
        amountOut = FullMath.mulDiv(amountIn, 2**96, sqrtpriceX96);
        amountOut = FullMath.mulDiv(amountOut, 2**96, sqrtpriceX96);
    }
    // amountOut = amountOut / 10**6;
    limitOrderId++;

    limitOrderToken.mint(msg.sender, limitOrderId);


    limitOrders[limitOrderId] = LimitOrder({
        pool: pool,
        id: limitOrderId,
        tick: tick,
        sqrtpriceX96: uint256(sqrtpriceX96),
        zeroForOne: zeroForOne,
        amountIn: amountIn,
        amountOut: amountOut,
        claimedAmount: 0,
        status: LimitOrderStatus.active
    });

    emit CreateLImitOrder(address(pool), tick, limitOrderId);
    return limitOrderId;
}

Chức năng này dường như được sử dụng để tạo một lệnh giới hạn trên nhóm thanh khoản tập trung. Lệnh giới hạn (Limit Order) là lệnh mua hoặc bán một lượng tài sản cụ thể ở một mức giá cụ thể hoặc tốt hơn. Trong trường hợp này, đơn đặt hàng dành cho một giá trị đánh dấu cụ thể trên nhóm thanh khoản. Giá trị tick đại diện cho giá của tài sản. Các tham số LowerOld upperOld tương ứng biểu thị các giá trị đánh dấu bên dưới và bên trên giá trị đánh dấu đã chỉ định. Tham số gốc chỉ định xem mã thông báo có phải là gốc hay không. Tham số amountIn biểu thị số lượng tài sản được mua hoặc bán theo thứ tự và tham số zeroForOne chỉ định hướng của thứ tự (cho dù đó là lệnh mua hay bán). Hàm trả về ID đơn đặt hàng giới hạn.

Câu lệnh yêu cầu đầu tiên là kiểm tra xem địa chỉ của tham số pool có tồn tại trong ánh xạ pools của hợp đồng masterDeployer hay không. Nếu nó không tồn tại, hàm sẽ báo lỗi với thông báo “INVALID_POOL“. Câu lệnh yêu cầu thứ hai đang kiểm tra xem tham số amountIn có khác 0 không. Nếu nó bằng 0, hàm sẽ đưa ra lỗi với thông báo “Amount:Zero“.

Sau đó, chúng tôi lưu trữ địa chỉ của tham số pool trong biến cachedPool msg.sender trong biến cachedMsgSender.

Tiếp đó, chúng tôi gọi hàm getPriceAndNearestTicks() trên hợp đồng nhóm, trả về giá hiện tại của nhóm.
Sau đó, chúng tôi gọi hàm getSqrtRatioAtTick() trên hợp đồng TickMath, chuyển cho nó tham số đánh dấu. Hàm này trả về sqrtpriceX96 tại dấu tích đã cho.

Sau đó gọi hàm createLimitOrder() trên hợp đồng nhóm, chuyển cho nó các tham số tick, LowerOld, upperOld, amountIn, native zeroForOne.

Tiếp đó chúng ta khai báo biến amountOut trước. Sau đó, chúng tôi kiểm tra giá trị của tham số zeroForOne. Nếu là true, nó sẽ kiểm tra xem sqrtpriceX96 có lớn hơn giá nhóm hiện tại hay không và tính toán amountOut là tích của amountIn, sqrtpriceX96 và sqrtpriceX96, chia bằng 2 lũy thừa 96. Nếu zeroForOnefalse, thì nó làm ngược lại: nó kiểm tra xem sqrtpriceX96 có thấp hơn giá nhóm hiện tại không và tính toán amountOut dưới dạng tích của amountIn và 2 mũ 96, chia cho sqrtpriceX96 và sqrtpriceX96.

Sau đó, mã sẽ tăng limitOrderId và đúc mã thông báo thứ tự giới hạn mới, chuyển nó msg.senderlimitOrderId mới.

Sau đó, chúng tôi thêm một mục vào ánh xạ limitOrders với các chi tiết của lệnh giới hạn mới được tạo. Sau đó, nó phát ra sự kiện CreateLimitOrder và trả về ID đơn đặt hàng giới hạn.

ConcentratedLiquidityPool.sol

Hàm createLimitOrder():

function createLimitOrder(
    int24 tick,
    int24 lowerOld,
    int24 upperOld,
    uint256 amountIn,
    bool native,
    bool zeroForOne
) external lock {
    Validators._ensureValidLimitOrderTick(ticks, limitOrderTicks, tickSpacing, tick, lowerOld, upperOld);

    CreateLImitOrderParams memory params = 
CreateLImitOrderParams({
        tick: tick,
	lowerOld: lowerOld,
	upperOld: upperOld,
	zeroForOne: zeroForOne,
	amountIn: amountIn,
	price: price,
	nearestTick: nearestTick,
	tickCount: tickCount
    });

    CreateLImitOrderParams memory params = 
CreateLImitOrderParams({
        tick: tick,
	lowerOld: lowerOld,
	upperOld: upperOld,
	zeroForOne: zeroForOne,
	amountIn: amountIn,
	price: price,
	nearestTick: nearestTick,
        tickCount: tickCount
    });

    (tickCount, nearestTick) = Ticks.insertLimitOrderTick(ticks, limitOrderTicks, params);
    ILimitOrderManager(msg.sender).limitOrderCallback(zeroForOne ? token0 : token1, amountIn, native);

    if (zeroForOne) {
        limitOrderReserve0 += amountIn;
        if (reserve0 + limitOrderReserve0 > _balance(token0)) revert Token0Missing();
    } else {
        limitOrderReserve1 += amountIn;
        if (reserve1 + limitOrderReserve1 > _balance(token1)) revert Token1Missing();
    }
    emit CreateLimitOrder(tick, amountIn, zeroForOne);
}

Chúng ta lấy các tham số của hàm này từ hàm createLimitOrder() của hợp đồng LimitOrderManager. Chức năng này là bên ngoài và chúng tôi sử dụng công cụ sửa đổi khóa để tránh truy cập lại.

Tiếp theo này kiểm tra tính hợp lệ của giá trị đánh dấu lệnh giới hạn dựa trên một vài lần kiểm tra.

Sau đó, chúng tôi chuyển các giá trị tick, LowerOld, upperOld, zeroForOne, amountIn, price, nearestTick và tickCount vào cấu trúc CreateLImitOrder.

Tiếp đó chúng tôi đang chèn một đánh dấu lệnh giới hạn vào danh sách các đánh dấu được liên kết. Chúng tôi sử dụng thư viện insertLimitOrderTick of thư viện Ticks cho mục đích này. Sau đó, chúng tôi sử dụng chức năng limitOrderCallback của LimitOrderManager để gọi lại tùy thuộc vào zeroForOne, amountInnative.

Sau đó, dựa trên zeroForOne, chúng tôi tăng reserve tương ứng theo số tiền đã tính.

Sau đó, chúng tôi kiểm tra xem tổng của reservelimitOrderReserve tương ứng có lớn hơn số dư của mã thông báo tương ứng hay không. Nếu trường hợp này xảy ra, mã sẽ trở lại với lỗi Token0Missing hoặc Token1Missing, tùy thuộc vào mã thông báo nào bị thiếu.

Sau đó, chúng tôi bắn ra sự kiện CreateLimitOrder.

Tick

Chúng tôi sử dụng chức năng insertLimitOrderTick() để chèn Limit Order Tick mới vào đường cong. Hàm này trả về tickCount và nearestTick.

Hàm insertLimitOrderTick():

function insertLimitOrderTick(
    mapping(int24 => IConcentratedLiquidityPoolStruct.Tick) storage ticks,
    mapping(int24 => IConcentratedLiquidityPoolStruct.LimitOrderTickData) storage limitOrderTicks,
        ILimitOrderStruct.CreateLImitOrderParams calldata params
) public returns (uint256, int24) {
    int24 nearestTick = params.nearestTick;
    uint256 tickCount = params.tickCount;
    uint128 currentNormalLiquidity = ticks[params.tick].liquidity;
    uint256 token0limitOrderLiquidity = limitOrderTicks[params.tick].token0Liquidity;
    uint256 token1limitOrderLiquidity = limitOrderTicks[params.tick].token1Liquidity;
    if (
        currentNormalLiquidity != 0 ||
        limitOrderTicks[params.tick].isActive ||
        params.tick == TickMath.MAX_TICK ||
        params.tick == TickMath.MIN_TICK
    ) {
        if (params.zeroForOne) {
            token0limitOrderLiquidity += params.amountIn;
        } else {
            token1limitOrderLiquidity += params.amountIn;
        }
    } else {
        // We are inserting a new tick.
        IConcentratedLiquidityPoolStruct.Tick storage oldLower = ticks[lowerOld];
        IConcentratedLiquidityPoolStruct.Tick storage oldUpper = ticks[upperOld];

        require(
            (oldLower.liquidity != 0 || lowerOld == TickMath.MIN_TICK || limitOrderTicks[lowerOld].isActive) &&
            (oldUpper.liquidity != 0 || upperOld == TickMath.MAX_TICK || limitOrderTicks[upperOld].isActive) &&
            (lowerOld < tick) &&
            (tick < oldLower.nextTick) &&
            (oldUpper.previousTick < tick) &&
            (upperOld > tick),
            "LUO"
        );

        if (zeroForOne) {
            token0limitOrderLiquidity = amountIn;
        } else {
            token1limitOrderLiquidity = amountIn;
        }

        ticks[tick] = IConcentratedLiquidityPoolStruct.Tick(lowerOld, upperOld, 0, 0, 0, 0);
        oldLower.nextTick = tick;
        ticks[upperOld].previousTick = tick;

        int24 tickAtPrice = TickMath.getTickAtSqrtRatio(params.price);

        if (params.nearestTick < tick && tick <= tickAtPrice) {
            params.nearestTick = tick;
        }
        unchecked {
            params.tickCount++;
        }
    }

    if (params.zeroForOne) {
        limitOrderTicks[params.tick].token0Liquidity = token0limitOrderLiquidity;
    } else {
        limitOrderTicks[params.tick].token1Liquidity = token1limitOrderLiquidity;
    }
    if (!limitOrderTicks[params.tick].isActive) limitOrderTicks[params.tick].isActive = true;

    return (tickCount, nearestTick);
}

Hàm này có tham số như sau:

  • ticks: ánh xạ lưu trữ từ số nguyên sang cấu trúc IConcentratedLiquidityPoolStruct.Tick. Ánh xạ này được sử dụng để lưu trữ dữ liệu đánh dấu.
  • limitOrderTicks: ánh xạ lưu trữ từ số nguyên sang cấu trúc IConcentratedLiquidityPoolStruct.LimitOrderTickData. Ánh xạ này được sử dụng để lưu trữ dữ liệu đánh dấu thứ tự giới hạn.
  • CreateLImitOrderParams : cấu trúc chứa createLimitOrderParams

Đầu tiên, chúng tôi nêu tất cả các biến cục bộ cần thiết. Các biến recentTick tickCount được khởi tạo với các giá trị từ cấu trúc params, currentNormalLiquidity được khởi tạo với giá trị thanh khoản cho tick hiện tại từ ánh xạ tick và token0limitOrderLiquidity token1limitOrderLiquidity được khởi tạo với các giá trị thanh khoản token 0 và token 1 cho tick hiện tại từ ánh xạ limitOrderTicks.

Mã này kiểm tra xem dấu tích hiện tại đã tồn tại trong ánh xạ chưa. Nó kiểm tra xem tính thanh khoản của tick hiện tại trong ánh xạ ticks có khác không hay không, tick có hoạt động trong ánh xạ limitOrderTicks hay không và liệu tick có bằng giá trị tick tối thiểu hay tối đa hay không. Nếu bất kỳ điều kiện nào trong số này là đúng, điều đó có nghĩa là dấu kiểm hiện tại đã tồn tại trong ánh xạ và do đó không cần phải chèn dấu kiểm mới. Trong trường hợp này, mã cập nhật giá trị token0Liquidity hoặc token1Liquidity của ánh xạ limitOrderTicks, tùy thuộc vào giá trị của tham số zeroForOne.

Nếu đánh dấu hiện tại chưa tồn tại trong ánh xạ, thì mã sẽ nhập khối else này, khối này sẽ chèn đánh dấu mới vào ánh xạ tickslimitOrderTicks. Đầu tiên, nó truy xuất các tick oldLoweroldUpper từ ánh xạ ticks bằng cách sử dụng các giá trị lowerOldupperOld từ cấu trúc params. Sau đó, nó thực hiện một số kiểm tra để đảm bảo rằng dấu kiểm mới có thể được chèn vào ánh xạ. Những bước kiểm tra này bao gồm việc xác minh rằng dấu kiểm oldLoweroldUpper là hợp lệ và dấu kiểm mới nằm giữa chúng. Nếu bất kỳ kiểm tra nào trong số này không thành công, chức năng sẽ hủy bỏ với thông báo lỗi.

Nếu vượt qua kiểm tra, mã sẽ cập nhật giá trị token0Liquidity hoặc token1Liquidity của ánh xạ limitOrderTicks, tùy thuộc vào giá trị của tham số zeroForOne . Sau đó, nó sẽ chèn dấu kiểm mới vào ánh xạ ticks và cập nhật các giá trị nextTickpreviousTick của các dấu tích xung quanh trong ánh xạ ticks.

Mã này cũng tính toán tickAtPrice bằng cách sử dụng giá trị price từ cấu trúc params và hàm getTickAtSqrtRatio. Nếu dấu kiểm mới nằm giữa Tick gần nhất hiện tại và tickAtPrice, thì Tick gần nhất được cập nhật thành dấu kiểm mới. Cuối cùng, mã tăng tickCount và cập nhật nó trong cấu trúc params.

Sau khi chèn dấu kiểm mới vào ánh xạ, mã sẽ cập nhật giá trị token0Liquidity hoặc token1Liquidity của ánh xạ limitOrderTicks với giá trị đã được tính trước đó trong hàm. Nó cũng đặt giá trị isActive của tick trong ánh xạ limitOrderTicks thành true nếu nó chưa hoạt động. Cuối cùng, mã trả về các giá trị tickCount recentTick đã cập nhật.

LimitOrderMananger

Hàm limitOrderCallback():

function limitOrderCallback(address token, uint256 amount, bool native) external {
    require(msg.sender == cachedPool, "UNAUTHORIZED_CALLBACK");

    if (native) {
        _depositFromUserToVault(token, cachedMsgSender, msg.sender, amount);
    } else {
        vault.transfer(token, cachedMsgSender, msg.sender, amount);
    }
    cachedMsgSender = address(1);
    cachedPool = address(1);
}

Chức năng này là một chức năng gọi lại được kích hoạt khi một lệnh giới hạn được tạo.

Đầu tiên, hàm kiểm tra xem người gọi có phải là hợp đồng dự kiến hay không (cachedPool) bằng cách sử dụng câu lệnh require. Nếu người gọi không phải là hợp đồng dự kiến, chức năng sẽ hủy bỏ với một thông báo lỗi.

Tiếp theo, hàm kiểm tra giá trị của tham số native. Nếu là true, nó sẽ gọi hàm _depositFromUserToVault, chuyển token, cachedMsgSenderamount dưới dạng tranh luận. Nếu nativefalse, nó sẽ gọi hàm transfer trên hợp đồng vault, chuyển các đối số giống nhau.

Cuối cùng, hàm đặt lại các biến cachedMsgSendercachedPool về giá trị mặc định của chúng.

Thực hiện một lệnh Swap

Chức năng hoán đổi có trong hợp đồng CLP.

ConcentratedLiquidityPool

Hàm swap():

/// @dev Swaps one token for another. The router must prefund this contract and ensure there isn't too much slippage.
function swap(bytes memory data, bytes memory path) public lock returns (uint256 amountOut, uint256 amountIn) {
    (bool zeroForOne, address recipient, bool unwrapVault, int256 quantity) = abi.decode(data, (bool, address, bool, int256));

    SwapCache memory cache = SwapCache({
        feeAmount: 0,
        protocolFee: 0,
        feeGrowthGlobalA: zeroForOne ? feeGrowthGlobal1 : feeGrowthGlobal0,
        feeGrowthGlobalB: zeroForOne ? feeGrowthGlobal0 : feeGrowthGlobal1,
        currentPrice: uint256(price),
        currentLiquidity: uint256(liquidity),
        amountIn: quantity > 0 ? uint256(quantity) : 0,
        amountOut: quantity > 0 ? 0 : uint256(-quantity),
        totalAmount: 0,
        exactIn: quantity > 0,
        nextTickToCross: zeroForOne ? nearestTick : ticks[nearestTick].nextTick,
        limitOrderAmountOut: 0, //amount of output given by limitorder
        limitOrderAmountIn: 0, //amount of input limitorder consumed
        limitOrderReserve: zeroForOne ? limitOrderReserve0 : limitOrderReserve1
    });


    _updateSecondsPerLiquidity(cache.currentLiquidity);

    while ((cache.exactIn ? cache.amountIn != 0 : cache.amountOut != 0)) {

        SwapCacheLocal memory swapLocal = SwapCacheLocal({
            nextTickPrice: uint256(TickMath.getSqrtRatioAtTick(cache.nextTickToCross)),
            cross: false,
            fee: 0,
            amountIn: 0,
            amountOut: 0
        });

        require(cache.nextTickToCross != TickMath.MIN_TICK && cache.nextTickToCross != TickMath.MAX_TICK, "E4");
        (cache.amountOut, cache.currentPrice, swapLocal.cross, cache.amountIn, swapLocal.fee) = 
	    SwapExcecuter._executeConcentrateLiquidity(cache, zeroForOne, swapLocal.nextTickPrice, swapLocal.cross, swapFee);

        cache.totalAmount += cache.exactIn ? cache.amountOut : cache.amountIn;
        (cache.amountOut, cache.amountIn) = cache.exactIn ? (uint256(0), cache.amountIn) : (cache.amountOut, uint256(0))

        if (swapLocal.fee != 0)
            (cache.protocolFee, cache.feeGrowthGlobalB) = FeeHandler.handleFees(
                    FeeHandler.FeeHandlerRequest({
                    feeAmount: swapLocal.fee,
                    dfynFee: dfynFee,
                    protocolFee: cache.protocolFee,
                    currentLiquidity: cache.currentLiquidity,
                    feeGrowthGlobal: cache.feeGrowthGlobalB
                })
            );

        //checks limit order liquidity exist in next tick & excecutes if liquidity exist
        if (swapLocal.cross) {
	    {
	        SwapExcecuter.ExecuteLimitResponse memory response = SwapExcecuter._executeLimitOrder(
                    ExecuteLimitOrderParams({
	                sqrtpriceX96: swapLocal.nextTickPrice,
                        tick: cache.nextTickToCross,
	                zeroForOne: zeroForOne,
	                amountIn: cache.amountIn,
	                amountOut: cache.amountOut,
	                limitOrderAmountOut: cache.limitOrderAmountOut,
                        limitOrderAmountIn: cache.limitOrderAmountIn,
                        cross: swapLocal.cross,
                        token0LimitOrderFee: token0LimitOrderFee,
                        token1LimitOrderFee: token1LimitOrderFee,
                        exactIn: cache.exactIn,
                        limitOrderFee: limitOrderFee
	            }),
	            limitOrderTicks
	        );
	        // Set the state of cache and other variables in scope
                cache.amountIn = response.amountIn;
    	        cache.amountOut = response.amountOut;
                swapLocal.cross = response.cross;
                token0LimitOrderFee = response.token0LimitOrderFee;
                token1LimitOrderFee = response.token1LimitOrderFee;
                cache.limitOrderAmountOut = response.limitOrderAmountOut;
                cache.limitOrderAmountIn = response.limitOrderAmountIn;

                // reset amountIn and amountOut and update totalAmount
                cache.totalAmount += cache.exactIn ? cache.amountOut : cache.amountIn;
                (cache.amountOut, cache.amountIn) = cache.exactIn ? (uint256(0), cache.amountIn) : (cache.amountOut, uint256(0));
            }

            if (swapLocal.cross)
                (cache.currentLiquidity, cache.nextTickToCross) = Ticks.cross(
                    ticks,
                    Ticks.TickCrossRequest({
                        nextTickToCross: cache.nextTickToCross,
                        secondsGrowthGlobal: secondsGrowthGlobal,
                        currentLiquidity: cache.currentLiquidity,
                        feeGrowthGlobalA: cache.feeGrowthGlobalA,
                        feeGrowthGlobalB: cache.feeGrowthGlobalB,
                        zeroForOne: zeroForOne,
                        tickSpacing: tickSpacing
                    })
                );

            price = uint160(cache.currentPrice);

            int24 newNearestTick = zeroForOne ? cache.nextTickToCross : ticks[cache.nextTickToCross].previousTick;

            if (nearestTick != newNearestTick) {
                nearestTick = newNearestTick;
                liquidity = uint128(cache.currentLiquidity);
            }

            (amountOut, amountIn) = cache.exactIn ? (cache.totalAmount, uint256(quantity)) : (uint256(-quantity), cache.totalAmount);

            if (zeroForOne) {
                _transfer(token1, amountOut, recipient, unwrapVault);
                emit Swap(recipient, amountIn, amountOut, zeroForOne, cache.nextTickToCross);
            } else {
                _transfer(token0, amountOut, recipient, unwrapVault);
                emit Swap(recipient, amountIn, amountOut, zeroForOne, cache.nextTickToCross);
            }

            IDfynCallBack(msg.sender).swapCallBack(cache.exactIn, unwrapVault, amountIn, amountOut, path);

            require(
                amountIn ==
                    _balance(zeroForOne ? token0 : token1) -
                        ((zeroForOne ? reserve0 : reserve1) + (zeroForOne ? limitOrderReserve0 : limitOrderReserve1)),
                        "TM"
            );
          _updateFees(zeroForOne, cache.feeGrowthGlobalB, uint128(cache.protocolFee));
          _updateReserves(zeroForOne, uint128(amountIn), amountOut, cache.limitOrderAmountIn, cache.limitOrderAmountOut);
    // adding limitOrder liquidity with normal liquidity
}

DxDyMath: https://github.com/sushiswap/trident/blob/c405f3402a1ed336244053f8186742d2da5975e9/contracts/libraries/concentratedPool/DyDxMath.sol

Nguồn: Dfyn V2 Development Book

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 5 / 5. Số phiếu: 88

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén