З моменту анонсу UniswapV4 ця платформа обміну зазнала значних змін, перетворившись із простої платформи обміну на постачальника інфраструктурних послуг. Зокрема, функція Hooks у V4 привернула широку увагу. Після поглибленого дослідження я зібрав певний вміст, щоб допомогти всім краще зрозуміти цю трансформацію та її впровадження.
Інновації UniswapV4 спрямовані не лише на вдосконалення технології AMM, а й на розширення екосистеми. Зокрема, це нововведення включає такі ключові функції:
У наступних розділах я детально поясню значення цих функцій і принципи їх реалізації.
Джерело: https://twitter.com/jermywkh/status/1670779830621851650
UniswapV4 використовує метод ведення записів, схожий на подвійну бухгалтерію, щоб відстежувати зміни балансу токенів, що відповідають кожній операції. Цей метод подвійної бухгалтерії вимагає запису кожної транзакції на кількох рахунках одночасно та забезпечення збалансованості балансу активів між цими рахунками. Наприклад, припустимо, що користувач обмінює 100 TokenA на 50 TokenB із пулу. Запис у книзі буде таким:
У UniswapV4 цей метод ведення записів в основному використовується для основних операцій і змінної зберігання під назвою lockState.currencyDelta[валюта] використовується в коді для запису суми змін балансу токенів. Якщо значення цієї дельти додатне, це означає очікуване збільшення кількості токенів у пулі, тоді як від’ємне значення означає очікуване зменшення кількості токенів. Крім того, якщо значення додатне, воно вказує на кількість нестачі токенів у пулі (очікувана сума, яку потрібно отримати), тоді як від’ємне значення вказує на надлишок токенів у пулі (очікувана сума, яку користувачі можуть вилучити). У наведеному нижче списку показано вплив різних операцій на токен Delta:
Серед цих операцій лише «settle» і «take» передбачають фактичну передачу токенів, тоді як інші операції відповідають виключно за оновлення значення TokenDelta.
Тут ми використовуємо простий приклад, щоб проілюструвати, як оновити TokenDelta. Припустімо, що сьогодні ми обмінюємо 100 TokenA на 50 TokenB:
Коли вся операція обміну завершена, і TokenADelta, і TokenBDelta скидаються до 0. Це означає, що операцію було повністю збалансовано, таким чином забезпечуючи послідовність залишків на рахунку.
Раніше згадувалося, що UniswapV4 використовує змінні зберігання для запису TokenDelta. Однак у рамках контракту читання та запис до змінних зберігання є досить дорогими. Це підводить нас до іншого EIP, представленого Uniswap: EIP1153 - Transient Storage Opcodes.
UniswapV4 планує використовувати коди операцій TSTORE і TLOAD, надані EIP1153 для оновлення TokenDelta. Змінні пам’яті, які використовують коди операцій Transient Storage, будуть видалені після завершення транзакції (подібно до змінних пам’яті), таким чином зменшуючи плату за газ.
Було підтверджено, що EIP1153 буде включено до майбутнього оновлення в Канкуні, а UniswapV4 також заявив, що він запрацює після оновлення в Канкуні, як повідомляється тут.
джерело: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
UniswapV4 представляє механізм блокування, який означає, що перед виконанням будь-якої операції пулу ви повинні спочатку викликати PoolManager.lock(), щоб отримати блокування. Під час виконання lock() він перевіряє, чи значення TokenDelta дорівнює 0, інакше він повернеться. Після успішного отримання PoolManager.lock() він викликає функцію lockAcquired() msg.sender. Усередині функції lockAcquired() виконуються операції, пов’язані з пулом, такі як swap і modifyPosition.
Процес показано нижче. Коли користувачеві потрібно виконати операцію Token Swap, він повинен викликати смарт-контракт за допомогою функції lockAcquired() (іменованої контрактом зворотного виклику). Контракт зворотного виклику спочатку викликає PoolManager.lock(), а потім PoolManager викликає функцію lockAcquired() контракту зворотного виклику. Усередині функції lockAcquired() визначається логіка, пов’язана з операціями пулу, такими як своп, сеттл і тейк. Нарешті, коли lock() збирається закінчитися, PoolManager перевіряє, чи TokenDelta, пов’язаний із цією операцією, було скинуто до 0, гарантуючи, що баланс активів у пулі залишається незмінним.
Singleton Contract означає, що UniswapV4 відмовився від попередньої моделі Factory-Pool. Кожен пул більше не є незалежним смарт-контрактом, але всі пули мають спільний єдиний контракт. Цей дизайн у поєднанні з механізмом Flash Accounting вимагає лише оновлення необхідних змінних зберігання, що ще більше зменшує складність і вартість операцій.
У наведеному нижче прикладі, використовуючи як приклад UniswapV3, для обміну ETH на DAI знадобиться принаймні чотири передачі токенів (операції запису в сховище). Це включає численні зміни, зареєстровані для токенів USDC, USDT і DAI. Однак завдяки вдосконаленням UniswapV4 у поєднанні з механізмом Flash Accounting потрібна лише одна передача маркера (переміщення DAI з пулу до користувача), що значно зменшує кількість операцій і витрати.
Джерело: https://twitter.com/Uniswap/status/1671208668304486404
В останньому оновленні UniswapV4 найбільш помітною функцією є архітектура хуків. Це оновлення надає велику гнучкість щодо доступності пулу. Хуки — це додаткові дії, які запускаються через Контракт хуків під час виконання певних операцій у Пулі. Ці дії поділяються на ініціалізацію (створення пулу), modifyPosition (додавання/видалення ліквідності), обмін і пожертвування. Кожна категорія має дії до та після виконання.
Ця конструкція дозволяє користувачам виконувати спеціальну логіку до та після певних операцій, що робить її більш гнучкою та розширює функціональність UniswapV4.
Джерело: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf
Далі ми використаємо приклад лімітного замовлення, щоб пояснити фактичний процес роботи хуків у UniswapV4. Перш ніж почати, давайте коротко пояснимо принцип реалізації лімітних ордерів у UniswapV4.
Реалізація лімітного замовлення UniswapV4 працює шляхом додавання ліквідності до певного цінового діапазону, а потім виконання операції видалення ліквідності, якщо ліквідність у цьому діапазоні обмінюється.
Наприклад, скажімо, ми додаємо ліквідність у ціновому діапазоні 1900-2000 для ETH, а потім ціна ETH піднімається з 1800 до 2100. На даний момент всю ліквідність ETH, яку ми раніше додали в ціновому діапазоні 1900-2000, було обміняно на USDC (припускаємо, що в пулі ETH-USDC). Усунувши ліквідність у цей момент, ми можемо досягти ефекту, подібного до виконання ринкового ордера ETH за поточним ціновим діапазоном 1900-2000.
Цей приклад взято з GitHub UniswapV4. У цьому прикладі контракт Limit Order Hook надає два хуки, а саме afterInitialize і afterSwap. Хук afterInitialize використовується для запису цінового діапазону (тіку) під час створення пулу, щоб визначити, які лімітні ордери були зіставлені після обміну.
Коли користувачеві потрібно розмістити замовлення, контракт Hook виконує операцію додавання ліквідності на основі вказаного користувачем цінового діапазону та кількості. У контракті Hook для лімітних замовлень ви можете побачити функцію place() . Основна логіка полягає в тому, щоб викликати функцію lockAcquiredPlace() після отримання блокування для виконання операції додавання ліквідності, що еквівалентно розміщенню лімітного замовлення.
джерело: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L246
Після того як користувач заповнить токен Swap у цьому пулі, пул викличе функцію afterSwap() контракту Hook. Основна логіка afterSwap полягає у вилученні ліквідності раніше розміщених ордерів, які були виконані між попереднім ціновим діапазоном і поточним ціновим діапазоном. Така поведінка еквівалентна виконанню замовлення.
джерело: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
Ось блок-схема, яка ілюструє процес виконання лімітного замовлення:
Вище наведено повний процес впровадження лімітного ордера за допомогою механізму Hook.
Хуки мають кілька цікавих моментів, якими, на мій погляд, варто поділитися.
Рішення про виконання конкретних операцій до/після визначається крайнім лівим 1 байтом адреси контракту Hook. 1 байт дорівнює 8 бітам, що відповідає 8 додатковим діям. Пул перевірить, чи біт цієї дії дорівнює 1, щоб визначити, чи потрібно викликати відповідну функцію підключення в контракті підключення. Це також означає, що адреса Hook-контракту має бути розроблена певним чином і не може бути довільно обрана як Hook-контракт. Ця конструкція в основному спрямована на зменшення споживання газу та перенесення витрат на розгортання за контрактом для досягнення більш ефективної роботи. (PS: на практиці різні солі CREATE2 можна використовувати для грубого обчислення адрес контрактів, які відповідають умовам)
Окрім можливості виконувати додаткові операції до та після кожної дії, хуки також підтримують впровадження динамічних комісій. Під час створення пулу ви можете вказати, чи вмикати динамічні комісії. Якщо ввімкнено динамічні комісії, функція getFee() контракту Hook викликається під час обміну токенів. Контракт Hook може визначати розмір комісії, що стягується на основі поточного стану пулу. Така конструкція дозволяє здійснювати гнучкий розрахунок комісії на основі фактичних обставин, підвищуючи гнучкість системи.
Кожен пул повинен визначити контракт Hook під час створення, і його не можна змінити згодом (хоча різні пули можуть спільно використовувати той самий контракт Hook). Головним чином це відбувається тому, що хуки вважаються частиною PoolKey, а PoolManager використовує PoolKey, щоб визначити, з яким пулом працювати. Навіть якщо активи однакові, якщо контракт Hook відрізняється, він вважатиметься іншим пулом. Ця конструкція гарантує, що станом і операціями різних пулів можна керувати незалежно, забезпечуючи узгодженість пулів. Однак це також збільшує складність маршрутизації, оскільки збільшується кількість пулів (можливо, UniswapX призначений для вирішення цієї проблеми).
UniswapV4 чітко наголошує на розширенні всієї екосистеми Uniswap, перетворюючи її на інфраструктуру, яка дозволить створювати більше послуг на основі пулів Uniswap. Це сприяє підвищенню конкурентоспроможності Uniswap і знижує ризик альтернативних послуг. Однак чи досягне він очікуваного успіху, ще невідомо. Деякі основні моменти включають поєднання Flash Accounting і EIP1153, і ми вважаємо, що в майбутньому ці функції будуть використовувати більше служб, що призведе до різних сценаріїв застосування. Це основна концепція UniswapV4, і ми сподіваємося, що вона дає змогу глибше зрозуміти, як працює UniswapV4. Якщо в статті є якісь помилки, будь ласка, вказуйте на них. Ми також раді обговоренням і відгукам.
Нарешті, ми хотіли б подякувати Антону Ченгу та Пін Чену за перегляд статті та надання цінних відгуків!
З моменту анонсу UniswapV4 ця платформа обміну зазнала значних змін, перетворившись із простої платформи обміну на постачальника інфраструктурних послуг. Зокрема, функція Hooks у V4 привернула широку увагу. Після поглибленого дослідження я зібрав певний вміст, щоб допомогти всім краще зрозуміти цю трансформацію та її впровадження.
Інновації UniswapV4 спрямовані не лише на вдосконалення технології AMM, а й на розширення екосистеми. Зокрема, це нововведення включає такі ключові функції:
У наступних розділах я детально поясню значення цих функцій і принципи їх реалізації.
Джерело: https://twitter.com/jermywkh/status/1670779830621851650
UniswapV4 використовує метод ведення записів, схожий на подвійну бухгалтерію, щоб відстежувати зміни балансу токенів, що відповідають кожній операції. Цей метод подвійної бухгалтерії вимагає запису кожної транзакції на кількох рахунках одночасно та забезпечення збалансованості балансу активів між цими рахунками. Наприклад, припустимо, що користувач обмінює 100 TokenA на 50 TokenB із пулу. Запис у книзі буде таким:
У UniswapV4 цей метод ведення записів в основному використовується для основних операцій і змінної зберігання під назвою lockState.currencyDelta[валюта] використовується в коді для запису суми змін балансу токенів. Якщо значення цієї дельти додатне, це означає очікуване збільшення кількості токенів у пулі, тоді як від’ємне значення означає очікуване зменшення кількості токенів. Крім того, якщо значення додатне, воно вказує на кількість нестачі токенів у пулі (очікувана сума, яку потрібно отримати), тоді як від’ємне значення вказує на надлишок токенів у пулі (очікувана сума, яку користувачі можуть вилучити). У наведеному нижче списку показано вплив різних операцій на токен Delta:
Серед цих операцій лише «settle» і «take» передбачають фактичну передачу токенів, тоді як інші операції відповідають виключно за оновлення значення TokenDelta.
Тут ми використовуємо простий приклад, щоб проілюструвати, як оновити TokenDelta. Припустімо, що сьогодні ми обмінюємо 100 TokenA на 50 TokenB:
Коли вся операція обміну завершена, і TokenADelta, і TokenBDelta скидаються до 0. Це означає, що операцію було повністю збалансовано, таким чином забезпечуючи послідовність залишків на рахунку.
Раніше згадувалося, що UniswapV4 використовує змінні зберігання для запису TokenDelta. Однак у рамках контракту читання та запис до змінних зберігання є досить дорогими. Це підводить нас до іншого EIP, представленого Uniswap: EIP1153 - Transient Storage Opcodes.
UniswapV4 планує використовувати коди операцій TSTORE і TLOAD, надані EIP1153 для оновлення TokenDelta. Змінні пам’яті, які використовують коди операцій Transient Storage, будуть видалені після завершення транзакції (подібно до змінних пам’яті), таким чином зменшуючи плату за газ.
Було підтверджено, що EIP1153 буде включено до майбутнього оновлення в Канкуні, а UniswapV4 також заявив, що він запрацює після оновлення в Канкуні, як повідомляється тут.
джерело: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
UniswapV4 представляє механізм блокування, який означає, що перед виконанням будь-якої операції пулу ви повинні спочатку викликати PoolManager.lock(), щоб отримати блокування. Під час виконання lock() він перевіряє, чи значення TokenDelta дорівнює 0, інакше він повернеться. Після успішного отримання PoolManager.lock() він викликає функцію lockAcquired() msg.sender. Усередині функції lockAcquired() виконуються операції, пов’язані з пулом, такі як swap і modifyPosition.
Процес показано нижче. Коли користувачеві потрібно виконати операцію Token Swap, він повинен викликати смарт-контракт за допомогою функції lockAcquired() (іменованої контрактом зворотного виклику). Контракт зворотного виклику спочатку викликає PoolManager.lock(), а потім PoolManager викликає функцію lockAcquired() контракту зворотного виклику. Усередині функції lockAcquired() визначається логіка, пов’язана з операціями пулу, такими як своп, сеттл і тейк. Нарешті, коли lock() збирається закінчитися, PoolManager перевіряє, чи TokenDelta, пов’язаний із цією операцією, було скинуто до 0, гарантуючи, що баланс активів у пулі залишається незмінним.
Singleton Contract означає, що UniswapV4 відмовився від попередньої моделі Factory-Pool. Кожен пул більше не є незалежним смарт-контрактом, але всі пули мають спільний єдиний контракт. Цей дизайн у поєднанні з механізмом Flash Accounting вимагає лише оновлення необхідних змінних зберігання, що ще більше зменшує складність і вартість операцій.
У наведеному нижче прикладі, використовуючи як приклад UniswapV3, для обміну ETH на DAI знадобиться принаймні чотири передачі токенів (операції запису в сховище). Це включає численні зміни, зареєстровані для токенів USDC, USDT і DAI. Однак завдяки вдосконаленням UniswapV4 у поєднанні з механізмом Flash Accounting потрібна лише одна передача маркера (переміщення DAI з пулу до користувача), що значно зменшує кількість операцій і витрати.
Джерело: https://twitter.com/Uniswap/status/1671208668304486404
В останньому оновленні UniswapV4 найбільш помітною функцією є архітектура хуків. Це оновлення надає велику гнучкість щодо доступності пулу. Хуки — це додаткові дії, які запускаються через Контракт хуків під час виконання певних операцій у Пулі. Ці дії поділяються на ініціалізацію (створення пулу), modifyPosition (додавання/видалення ліквідності), обмін і пожертвування. Кожна категорія має дії до та після виконання.
Ця конструкція дозволяє користувачам виконувати спеціальну логіку до та після певних операцій, що робить її більш гнучкою та розширює функціональність UniswapV4.
Джерело: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf
Далі ми використаємо приклад лімітного замовлення, щоб пояснити фактичний процес роботи хуків у UniswapV4. Перш ніж почати, давайте коротко пояснимо принцип реалізації лімітних ордерів у UniswapV4.
Реалізація лімітного замовлення UniswapV4 працює шляхом додавання ліквідності до певного цінового діапазону, а потім виконання операції видалення ліквідності, якщо ліквідність у цьому діапазоні обмінюється.
Наприклад, скажімо, ми додаємо ліквідність у ціновому діапазоні 1900-2000 для ETH, а потім ціна ETH піднімається з 1800 до 2100. На даний момент всю ліквідність ETH, яку ми раніше додали в ціновому діапазоні 1900-2000, було обміняно на USDC (припускаємо, що в пулі ETH-USDC). Усунувши ліквідність у цей момент, ми можемо досягти ефекту, подібного до виконання ринкового ордера ETH за поточним ціновим діапазоном 1900-2000.
Цей приклад взято з GitHub UniswapV4. У цьому прикладі контракт Limit Order Hook надає два хуки, а саме afterInitialize і afterSwap. Хук afterInitialize використовується для запису цінового діапазону (тіку) під час створення пулу, щоб визначити, які лімітні ордери були зіставлені після обміну.
Коли користувачеві потрібно розмістити замовлення, контракт Hook виконує операцію додавання ліквідності на основі вказаного користувачем цінового діапазону та кількості. У контракті Hook для лімітних замовлень ви можете побачити функцію place() . Основна логіка полягає в тому, щоб викликати функцію lockAcquiredPlace() після отримання блокування для виконання операції додавання ліквідності, що еквівалентно розміщенню лімітного замовлення.
джерело: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L246
Після того як користувач заповнить токен Swap у цьому пулі, пул викличе функцію afterSwap() контракту Hook. Основна логіка afterSwap полягає у вилученні ліквідності раніше розміщених ордерів, які були виконані між попереднім ціновим діапазоном і поточним ціновим діапазоном. Така поведінка еквівалентна виконанню замовлення.
джерело: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
Ось блок-схема, яка ілюструє процес виконання лімітного замовлення:
Вище наведено повний процес впровадження лімітного ордера за допомогою механізму Hook.
Хуки мають кілька цікавих моментів, якими, на мій погляд, варто поділитися.
Рішення про виконання конкретних операцій до/після визначається крайнім лівим 1 байтом адреси контракту Hook. 1 байт дорівнює 8 бітам, що відповідає 8 додатковим діям. Пул перевірить, чи біт цієї дії дорівнює 1, щоб визначити, чи потрібно викликати відповідну функцію підключення в контракті підключення. Це також означає, що адреса Hook-контракту має бути розроблена певним чином і не може бути довільно обрана як Hook-контракт. Ця конструкція в основному спрямована на зменшення споживання газу та перенесення витрат на розгортання за контрактом для досягнення більш ефективної роботи. (PS: на практиці різні солі CREATE2 можна використовувати для грубого обчислення адрес контрактів, які відповідають умовам)
Окрім можливості виконувати додаткові операції до та після кожної дії, хуки також підтримують впровадження динамічних комісій. Під час створення пулу ви можете вказати, чи вмикати динамічні комісії. Якщо ввімкнено динамічні комісії, функція getFee() контракту Hook викликається під час обміну токенів. Контракт Hook може визначати розмір комісії, що стягується на основі поточного стану пулу. Така конструкція дозволяє здійснювати гнучкий розрахунок комісії на основі фактичних обставин, підвищуючи гнучкість системи.
Кожен пул повинен визначити контракт Hook під час створення, і його не можна змінити згодом (хоча різні пули можуть спільно використовувати той самий контракт Hook). Головним чином це відбувається тому, що хуки вважаються частиною PoolKey, а PoolManager використовує PoolKey, щоб визначити, з яким пулом працювати. Навіть якщо активи однакові, якщо контракт Hook відрізняється, він вважатиметься іншим пулом. Ця конструкція гарантує, що станом і операціями різних пулів можна керувати незалежно, забезпечуючи узгодженість пулів. Однак це також збільшує складність маршрутизації, оскільки збільшується кількість пулів (можливо, UniswapX призначений для вирішення цієї проблеми).
UniswapV4 чітко наголошує на розширенні всієї екосистеми Uniswap, перетворюючи її на інфраструктуру, яка дозволить створювати більше послуг на основі пулів Uniswap. Це сприяє підвищенню конкурентоспроможності Uniswap і знижує ризик альтернативних послуг. Однак чи досягне він очікуваного успіху, ще невідомо. Деякі основні моменти включають поєднання Flash Accounting і EIP1153, і ми вважаємо, що в майбутньому ці функції будуть використовувати більше служб, що призведе до різних сценаріїв застосування. Це основна концепція UniswapV4, і ми сподіваємося, що вона дає змогу глибше зрозуміти, як працює UniswapV4. Якщо в статті є якісь помилки, будь ласка, вказуйте на них. Ми також раді обговоренням і відгукам.
Нарешті, ми хотіли б подякувати Антону Ченгу та Пін Чену за перегляд статті та надання цінних відгуків!