Lời nói đầu
Với sự phát triển nhanh chóng của tài chính phi tập trung (DeFi), Uniswap, với tư cách là sàn giao dịch phi tập trung hàng đầu, đã đi đầu trong đổi mới. Bài viết này sẽ cung cấp phân tích chuyên sâu về cơ chế cốt lõi của giao thức Uniswap v3 và giải thích chi tiết về thiết kế chức năng của nó, bao gồm các chức năng chính như thanh khoản tập trung, nhiều tỷ giá, trao đổi mã thông báo và các khoản vay nhanh, đồng thời cung cấp các điểm kiểm tra có liên quan cho kiểm toán viên. (Lưu ý: Có thể xem hình ảnh trong bài viết này ở độ phân giải cao tại https://www.figma.com/board/QyIpAUR93MxZ4XZZf2QjDk/uniswap-v3.)
Phân tích kiến trúc
Giao thức Uniswap v3 chủ yếu bao gồm bốn mô-đun:
- Trình quản lý vị trí: Giao diện chính để người dùng thực hiện các hoạt động thanh khoản, qua đó người dùng có thể tạo nhóm mã thông báo, cung cấp/xóa thanh khoản và sử dụng ERC721 làm thông tin xác thực cho nhà cung cấp thanh khoản (LP).
- SwapRouter: Lối vào để người dùng trao đổi mã thông báo Người dùng có thể hoàn thành các hoạt động trao đổi mã thông báo thông qua mô-đun này.
- Pool: Chịu trách nhiệm thực hiện các giao dịch token, quản lý thanh khoản, thu phí giao dịch và các chức năng quản lý dữ liệu của Oracle. Trong số đó, cơ chế Tick chia phạm vi giá thành nhiều thang đo nhỏ.
- Factory: dùng để tạo và quản lý hợp đồng Pool.
Quá trình sắp xếp
Tạo một cặp mã thông báo
Người dùng có thể thực hiện việc này thông qua hàm createAndInitializePoolIfNecessary. Người dùng cần chuyển token0, token1, phí xử lý (phí) và giá ban đầu () của cặp mã thông báo. Đầu tiên, hệ thống sẽ kiểm tra xem cặp mã thông báo đã tồn tại hay chưa thông qua hàm getPool. Nếu nó chưa được tạo, createPool sẽ được gọi và lệnh CREATE2 sẽ được sử dụng để triển khai cặp giao dịch. Cuối cùng, hàm khởi tạo được sử dụng để hoàn thành việc khởi tạo giá, phí xử lý, đánh dấu, oracle và các tham số liên quan khác.
Cung cấp thanh khoản
Cung cấp thanh khoản
Người dùng có thể tạo các vị thế thanh khoản mới và tạo NFT tương ứng thông qua chức năng đúc tiền hoặc thêm thanh khoản vào các vị trí thanh khoản NFT hiện có thông qua chức năng tăng thanh khoản. Đầu tiên, hệ thống sẽ kiểm tra xem giao dịch có được thực hiện trong khoảng thời gian đã chỉ định hay không, sau đó gọi hàm addLiquidity để hoàn thành thao tác cụ thể. Trong chức năng này, địa chỉ và tính thanh khoản của nhóm trước tiên được tính toán, sau đó _updatePosition được gọi để cập nhật Vị trí của người dùng, sửa đổi đánh dấu dưới, trên và tổng phí xử lý tích lũy. Sau đó, hệ thống bổ sung tính thanh khoản thông qua _modifyPosition, đảm bảo rằng đánh dấu đáp ứng các điều kiện giới hạn trên và dưới, trả về số lượng token0 và token1 (int256) được tính toán và gửi nó đến nhóm. Cuối cùng, hệ thống cập nhật thông tin Vị trí tương ứng dựa trên tokenId của người dùng.
Loại bỏ thanh khoản
Người dùng có thể loại bỏ thanh khoản thông qua chức năng giảm thanh khoản. Đầu tiên, hệ thống sẽ kiểm tra thẩm quyền của chứng chỉ LP và hiệu lực về mặt thời gian của giao dịch. Với tiền đề đảm bảo rằng nhóm có đủ thanh khoản, hãy gọi chức năng đốt để loại bỏ thanh khoản. Sau đó, hệ thống sẽ xác minh xem số lượng mã thông báo thực tế bị xóa có đáp ứng các yêu cầu tối thiểu do người dùng đặt ra hay không và cập nhật thông tin Vị trí của người dùng cho phù hợp.
tráo đổi
Người dùng có thể chỉ định số lượng mã thông báo sẽ được thanh toán và số lượng mã thông báo tối thiểu dự kiến sẽ nhận được thông qua chức năng PrecisionInput hoặc chỉ định số lượng mã thông báo tối đa sẽ được thanh toán và đặt số lượng mã thông báo dự kiến sẽ nhận được thông qua chức năng chính xác. Trước tiên, hệ thống phân tích cú pháp đường dẫn (đường dẫn), sau đó gọi hàm chính xácInputInternal hoặc hàm chính xácOutputInternal theo trình tự để hoàn thành từng bước của thao tác hoán đổi.
Trong chức năng hoán đổi, trước tiên hệ thống sẽ khóa trạng thái mở khóa để ngăn các giao dịch khác can thiệp vào việc cập nhật các biến trạng thái. Sau khi vào vòng lặp, hệ thống sẽ tìm giá giao dịch tiếp theo thông qua đánh dấu và gọi hàm tínhSwapStep để tính tỷ giá trao đổi ở mỗi bước cho đến khi tokenIn hoặc tokenOut đạt được mong đợi của người dùng. Đồng thời, hệ thống sẽ cập nhật các giá trị liên quan về phí, thanh khoản, tick và giá. Nếu dấu tích thay đổi, dữ liệu Oracle cũng cần được cập nhật. Sau khi hoàn thành các thao tác này, hệ thống sẽ thanh toán tokenOut cho người dùng và người dùng thanh toán tokenIn thông qua chức năng gọi lại uniswapV3SwapCallback. Cơ chế này có thể được coi là một trao đổi nhanh. Sau đó, hệ thống sẽ kiểm tra xem số dư hợp đồng có khớp hay không và mở khóa trạng thái đã mở khóa sau khi xác nhận.
Một giao dịch kết thúc thành công khi tất cả các thao tác hoán đổi trong đường dẫn được hoàn thành và giao dịch đáp ứng được mong đợi của người dùng.
đèn flash
Người dùng có thể thực hiện các hoạt động cho vay flash thông qua các chức năng flash. Đầu tiên, hệ thống sẽ tính phí vay, sau đó gửi mã thông báo mà người dùng yêu cầu đến địa chỉ cho vay được chỉ định. Tiếp theo, hệ thống gọi lại hàm uniswapV3FlashCallback do người dùng thực hiện và người dùng hoàn tất thao tác trả nợ trong hàm này. Hệ thống sẽ kiểm tra sự thay đổi số dư hợp đồng sau khi gọi lại để đảm bảo rằng nó phù hợp với số tiền vay của người dùng và cập nhật phí xử lý tương ứng. Ngoài chức năng flash, người dùng cũng có thể thực hiện các chức năng flash loan tương tự thông qua các hoạt động hoán đổi, tức là vay trước rồi trả lại mã thông báo trong quá trình giao dịch.
Điểm kiểm tra
Điểm kiểm tra
1. Kiểm tra xem returnETH có được gọi sau thao tác hoán đổi hay không
Trong hàm PrecisionInput, người dùng cần chỉ định số lượng token cần thanh toán và số lượng token tối thiểu dự kiến nhận được. Trước khi gọi uniswapV3SwapCallback, hệ thống sẽ tính toán lại money0 và money1 để đảm bảo người dùng có thể gửi token một cách chính xác. Tuy nhiên, khi trao đổi bằng ETH, người dùng cần gửi ETH cùng với giao dịch. Ngay cả khi tất cả ETH không được sử dụng trong quá trình giao dịch, chức năng sẽ không tự động trả lại số dư thừa. Hàm PrecisionInput chỉ trả về số tiềnOut, vì vậy các nhà giao dịch không thể trực tiếp biết sàn giao dịch này thực sự đã tiêu thụ bao nhiêu ETH.
Ngoài ra, bất kỳ ai cũng có thể gọi hàm returnETH để rút ETH chưa sử dụng khỏi hợp đồng. Do đó, nên kiểm tra xem returnETH có được gọi sau thao tác hoán đổi hay không để ngăn người dùng bỏ lại ETH không sử dụng trong giao thức hoặc sử dụng chức năng MultiCall để hoàn thành nhiều lệnh gọi hàm trong một thao tác.
2. Kiểm tra xem TWAP có được triển khai để nhận được giá oracle hay không
Khi sử dụng Uniswap làm nguồn giá, có thể có nguy cơ thao túng giá khi giao thức bên ngoài truy cập trực tiếp vào Slot0 để lấy sqrtPriceX96. Những kẻ tấn công có thể thao túng trạng thái của nhóm thanh khoản thông qua hoán đổi và các phương thức khác để có được mức giá ưu đãi khi thực hiện giao dịch.
Để giảm rủi ro này, các nhà phát triển nên triển khai thêm Giá trung bình có trọng số theo thời gian (TWAP) để có được giá, vì TWAP có thể giảm tác động của biến động giá dữ dội trong thời gian ngắn một cách hiệu quả, khiến việc thao túng giá trở nên khó khăn hơn.
3. Nên cho phép người dùng tự thiết lập thông số trượt giá
Khi các giao thức khác sử dụng Uniswap v3 cho các hoạt động hoán đổi, các nhà phát triển nên đặt tính năng chống trượt giá dựa trên các tình huống kinh doanh và cho phép người dùng tự điều chỉnh các tham số để ngăn chặn các cuộc tấn công sandwich. Trong hàm hoán đổi này, tham số thứ tư sqrtPriceLimitX96 được sử dụng để chỉ định mức giá tối thiểu hoặc tối đa mà người dùng sẵn sàng thực hiện hoán đổi. Thông số này có thể ngăn chặn một cách hiệu quả những biến động giá cực lớn trong quá trình giao dịch, từ đó giảm tổn thất cho người dùng do trượt giá quá mức.
4. Nên giới thiệu cơ chế danh sách trắng nhóm thanh khoản
Trong Uniswap v3, cùng một cặp mã thông báo ERC20 có thể tồn tại trong nhiều nhóm thanh khoản (Pool) cùng lúc dựa trên các mức phí khác nhau. Thông thường, một số nhóm thanh khoản nắm giữ phần lớn thanh khoản, trong khi các nhóm khác có thể có Tổng khối lượng bị khóa (TVL) rất ít hoặc thậm chí chưa được tạo. Những nhóm TVL thấp hơn này có nhiều khả năng trở thành mục tiêu để thao túng giá.
Do đó, khi các bên tham gia dự án chọn sử dụng dữ liệu nhóm thanh khoản, họ nên tránh chỉ sử dụng LP làm nguồn dữ liệu. Để đảm bảo độ tin cậy của dữ liệu, nên giới thiệu cơ chế danh sách trắng để sàng lọc các nhóm có đủ thanh khoản và khó thao tác. Cơ chế này có thể giảm thiểu rủi ro đáng kể, đảm bảo tính bảo mật và chính xác của dữ liệu tham chiếu giá, đồng thời ngăn ngừa những tổn thất tiềm ẩn do thao túng các nhóm có TVL quá thấp.
5. Kiểm tra xem tính năng bỏ chọn có được sử dụng trong TickMath.sol, FullMath.sol và Location.sol không
Các mô-đun như TickMath, FullMath và Vị trí được sử dụng trong Uniswap v3 để thực hiện các phép tính toán học phức tạp dựa trên cơ chế xử lý tràn trong Solidity. Trong các phiên bản trước của Solidity (<0.8.0), hành vi tràn số nguyên và tràn số nguyên không đưa ra ngoại lệ theo mặc định, do đó mã có thể chạy bình thường dựa trên giả định này. Tuy nhiên, bắt đầu từ phiên bản Solidity 0.8.0, tràn và tràn sẽ tự động đưa ra các ngoại lệ, điều này sẽ ảnh hưởng đến việc thực thi mã hiện có. Để đảm bảo rằng các mô-đun này hoạt động bình thường trong Solidity 0.8.0 trở lên, nhà phát triển cần tắt tính năng kiểm tra tràn theo cách thủ công bằng cách sử dụng các khối mã không được kiểm tra trong các chức năng cụ thể. Điều này khôi phục hành vi từ các phiên bản trước và đảm bảo thực hiện hiệu quả các hoạt động nhạy cảm với lỗi tràn.
Hỗ trợ và điều chỉnh chính thức đã được thực hiện cho Solidity 0.8.0 trở lên. Để biết chi tiết, vui lòng xem bản cập nhật này (https://github.com/Uniswap/v3-core/commit/6562c52e8f75f0c10f9deaf44861847585fc8129). Thay đổi này đảm bảo rằng TickMath, FullMath và các mô-đun liên quan khác sẽ tiếp tục chạy chính xác trong các phiên bản mới của trình biên dịch.
6. Kiểm tra xem phương pháp mã hóa và giải mã đường dẫn có giống nhau không
Trong hàm PrecisionInput và PrecisionOutput của Uniswap v3, người dùng cần nhập tham số đường dẫn, tham số này phải được mã hóa và giải mã theo định dạng cố định là tokenA-fee-tokenB để thực hiện các hoạt động trao đổi token theo từng bước. Cấu trúc đường dẫn này chỉ định rõ ràng hai mã thông báo liên quan đến mỗi bước giao dịch và mức phí giữa chúng. Nếu giao thức bên ngoài chọn phương thức giải mã đường dẫn khác khi sử dụng chức năng trao đổi mã thông báo của Uniswap v3, điều đó có thể dẫn đến định dạng đường dẫn không nhất quán với định dạng đường dẫn dự kiến của Uniswap. Trong trường hợp này, giao thức có thể không giải quyết được đường dẫn một cách chính xác và do đó không thực hiện thành công hoạt động trao đổi mã thông báo dự kiến.
Do đó, các nhà phát triển nên đảm bảo rằng các giao thức bên ngoài tuân thủ nghiêm ngặt các quy tắc mã hóa đường dẫn của Uniswap khi tích hợp chức năng trao đổi token của Uniswap v3. Để tránh lỗi giải mã đường dẫn, các giao thức bên ngoài nên kiểm tra cẩn thận định dạng của tham số đường dẫn khi gọi chính xácInput và chính xácOutput để tránh giao dịch thất bại hoặc kết quả không mong muốn.
Do đó, các nhà phát triển nên đảm bảo rằng các giao thức bên ngoài tuân thủ nghiêm ngặt các quy tắc mã hóa đường dẫn của Uniswap khi tích hợp chức năng trao đổi token của Uniswap v3. Để tránh lỗi giải mã đường dẫn, các giao thức bên ngoài nên kiểm tra cẩn thận định dạng của tham số đường dẫn khi gọi chính xácInput và chính xácOutput để tránh giao dịch thất bại hoặc kết quả không mong muốn.
7. Kiểm tra xem thứ tự mã thông báo có ảnh hưởng đến logic dự án hay không
Trong Uniswap, token0 là mã thông báo có thứ tự thấp hơn và được sử dụng làm mã thông báo cơ sở, trong khi token1 là mã thông báo có thứ tự cao hơn và được sử dụng làm mã thông báo báo giá. Uniswap sắp xếp địa chỉ của hai mã thông báo theo từ điển để đảm bảo rằng thứ tự của các cặp mã thông báo luôn nhất quán trong nhóm.
Tuy nhiên, do địa chỉ hợp đồng của cùng một mã thông báo trên các mạng blockchain khác nhau có thể khác nhau, đặc biệt đối với các hợp đồng được triển khai trên các chuỗi nên thứ tự sắp xếp mã thông báo có thể thay đổi. Thay đổi này sẽ khiến vai trò của token0 và token1 bị đảo ngược, do đó ảnh hưởng đến hiệu suất giá. Ví dụ: trên một số chuỗi, một mã thông báo cụ thể có thể là token0, nhưng trên các chuỗi khác, nó có thể được đặt hàng dưới dạng mã thông báo 1, dẫn đến mối quan hệ khác giữa mã thông báo cơ sở và mã thông báo được trích dẫn, cuối cùng ảnh hưởng đến giá hiển thị. Do đó, các nhà phát triển nên kiểm tra xem thứ tự mã thông báo có ảnh hưởng đến logic dự án hay không. Đặc biệt trong môi trường chuỗi chéo, hãy nhớ xem xét các vấn đề về giá có thể do thứ tự mã thông báo gây ra để tránh những ảnh hưởng bất lợi đến hiệu suất giá và logic giao dịch.
Tóm tắt
Các mục kiểm tra cơ bản ở trên dựa trên phiên bản hiện tại của Uniswap v3 và được kiểm toán viên sử dụng để kiểm tra các dự án tương tác với Uniswap v3. Việc thực hiện các dự án khác nhau có những đặc điểm riêng nên kiểm toán viên cần hiểu biết sâu sắc về thỏa thuận và tiến hành kiểm tra nghiêm ngặt dựa trên tình hình thực tế. Đối với các dự án đang được phát triển, nhóm bảo mật SlowMist khuyến nghị các nhà phát triển nên cân nhắc cẩn thận các bước kiểm tra này trong quá trình phát triển để đảm bảo tính bảo mật và độ tin cậy của giao thức.
Tác giả |
Biên tập viên Liz |
Tất cả bình luận