С момента анонса UniswapV4 эта своп-платформа претерпела значительные изменения, превратившись из простой своп-платформы в поставщика инфраструктурных услуг. В частности, широкое внимание привлекла функция Hooks в V4. Проведя глубокое исследование, я собрал некоторые материалы, чтобы помочь всем лучше понять эту трансформацию и ее реализацию.
Инновации UniswapV4 направлены не только на улучшение технологии AMM, но и на расширение экосистемы. В частности, эта инновация включает в себя следующие ключевые особенности:
В следующих разделах я подробно расскажу о значении этих функций и принципах их реализации.
source: https://twitter.com/jermywkh/status/1670779830621851650
В UniswapV4 используется метод учета, похожий на метод двойной записи, для отслеживания изменений баланса токенов, соответствующих каждой операции. Этот метод двойной записи требует одновременного отражения каждой операции на нескольких счетах и обеспечения баланса активов между этими счетами. Например, предположим, что пользователь обменивает 100 TokenA на 50 TokenB из пула. Запись в бухгалтерской книге будет выглядеть следующим образом:
В UniswapV4 этот метод ведения записей используется в основном для основных операций, и переменная хранения с именем lockState.currencyDelta[currency] используется в коде для записи суммы изменений баланса токенов. Если значение этой дельты положительное, оно представляет собой ожидаемое увеличение количества токенов в пуле, в то время как отрицательное значение представляет собой ожидаемое уменьшение количества токенов. Как вариант, если значение положительное, оно указывает на недостаток токенов в пуле (ожидаемая сумма для получения), а отрицательное значение указывает на избыток токенов в пуле (ожидаемая сумма для снятия пользователями). В следующем списке показано влияние различных операций на Token Delta:
Среди этих операций только "settle" и "take" связаны с фактической передачей токенов, в то время как остальные операции отвечают только за обновление значения TokenDelta.
Здесь мы используем простой пример, чтобы проиллюстрировать, как обновить TokenDelta. Предположим, что сегодня мы обменяли 100 TokenA на 50 TokenB:
Когда вся операция обмена завершена, TokenADelta и TokenBDelta обнуляются до 0. Это означает, что операция была полностью сбалансирована, что обеспечивает постоянство баланса счетов.
Ранее упоминалось, что UniswapV4 использует переменные хранения для записи TokenDelta. Однако в рамках контракта чтение и запись в переменные хранения стоят довольно дорого. Это подводит нас к еще одному EIP, представленному Uniswap: EIP1153 - опкоды переходного хранения.
UniswapV4 планирует использовать опкоды TSTORE и TLOAD, предоставленные EIP1153, для обновления TokenDelta. Переменные хранения, использующие опкоды переходного хранения, будут отбрасываться после завершения транзакции (аналогично переменным памяти), что позволит снизить плату за газ.
Было подтверждено, что EIP1153 будет включен в предстоящее обновление Cancun, и UniswapV4 также заявил, что он появится после обновления Cancun, как сообщается здесь.
источник: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
В UniswapV4 появился механизм блокировки, который означает, что перед выполнением любой операции с пулом Вы должны сначала вызвать PoolManager.lock(), чтобы получить блокировку. Во время выполнения lock() проверяется, равно ли значение TokenDelta 0, в противном случае произойдет возврат. После того, как PoolManager.lock() успешно получен, он вызывает функцию lockAcquired() в msg.sender. Внутри функции lockAcquired() выполняются операции, связанные с пулом, такие как swap и modifyPosition.
Этот процесс показан ниже. Когда пользователю необходимо выполнить операцию обмена токенов, он должен вызвать смарт-контракт с функцией lockAcquired() (именуемый Контрактом обратного вызова). Контракт обратного вызова сначала вызывает PoolManager.lock(), а затем PoolManager вызывает функцию lockAcquired() контракта обратного вызова. Внутри функции lockAcquired() определяется логика, связанная с операциями Pool, такими как swap, settle и take. Наконец, когда блокировка() подходит к концу, PoolManager проверяет, была ли TokenDelta, связанная с этой операцией, сброшена в 0, гарантируя, что баланс активов в Пуле останется нетронутым.
Singleton Contract означает, что UniswapV4 отказался от предыдущей модели Factory-Pool. Каждый пул больше не является независимым смарт-контрактом, но все пулы используют один единственный контракт. Такая конструкция, в сочетании с механизмом Flash Accounting, требует только обновления необходимых переменных хранения, что еще больше снижает сложность и стоимость операций.
В приведенном ниже примере с использованием UniswapV3 обмен ETH на DAI потребует как минимум четыре передачи токенов (операции записи в хранилище). Сюда входят многочисленные изменения, зафиксированные для токенов USDC, USDT и DAI. Однако благодаря усовершенствованиям в UniswapV4 в сочетании с механизмом Flash Accounting требуется только одна передача токена (перемещение DAI от пула к пользователю), что значительно сокращает количество операций и расходы.
source: https://twitter.com/Uniswap/status/1671208668304486404
В последнем обновлении UniswapV4 наиболее заметной особенностью является архитектура крючков. Это обновление обеспечивает большую гибкость в плане доступности Пула. Крючки - это дополнительные действия, которые запускаются через Контракт крючков при выполнении определенных операций с Пулом. Эти действия делятся на initialize (создание пула), modifyPosition (добавление/удаление ликвидности), swap и donate. В каждой категории есть действия до и после выполнения.
Такая конструкция позволяет пользователям выполнять пользовательскую логику до и после определенных операций, что делает ее более гибкой и расширяет функциональность 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
После того, как пользователь завершит обмен токенами в этом пуле, пул вызовет функцию afterSwap() контракта Hook. Основная логика afterSwap заключается в том, чтобы удалить ликвидность ранее размещенных ордеров, которые были исполнены между предыдущим и текущим ценовым диапазоном. Такое поведение эквивалентно тому, что заказ будет выполнен.
источник: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
Вот блок-схема, иллюстрирующая процесс выполнения лимитного ордера:
Выше описан весь процесс реализации Limit-Order с помощью механизма Hook.
У Хукса есть несколько интересных моментов, которыми, на мой взгляд, стоит поделиться.
Решение о выполнении определенных операций до/после определяется крайним левым 1 байтом адреса контракта Hook. 1 байт равен 8 битам, что соответствует 8 дополнительным действиям. Пул будет проверять, равен ли бит этого действия 1, чтобы определить, вызывать ли соответствующую функцию крючка контракта Hook. Это также означает, что адрес контракта Hook должен быть разработан особым образом и не может быть произвольно выбран в качестве контракта Hook. Эта конструкция в основном направлена на снижение потребления газа и перенос затрат на контрактное развертывание для достижения более эффективной работы. (PS: На практике различные соли CREATE2 могут быть использованы для перебора адресов контрактов, удовлетворяющих условиям)
Помимо возможности выполнять дополнительные операции до и после каждого действия, крючки также поддерживают реализацию динамических сборов. При создании пула Вы можете указать, нужно ли включить динамическую оплату. Если динамические комиссии включены, при обмене токенов вызывается функция getFee() контракта Hook. Контракт с Крюком может определять размер взимаемой платы в зависимости от текущего состояния Пула. Такая конструкция позволяет гибко рассчитывать плату в зависимости от фактических обстоятельств, что повышает гибкость системы.
Каждый пул должен определить контракт с крючком во время своего создания, и он не может быть изменен после этого (хотя разные пулы могут использовать один и тот же контракт с крючком). Это происходит потому, что крючки считаются частью PoolKey, а PoolManager использует PoolKey, чтобы определить, с каким пулом работать. Даже если активы одинаковы, если договор Hook отличается, он будет считаться другим Pool. Такая конструкция позволяет управлять состоянием и операциями разных пулов независимо друг от друга, обеспечивая согласованность пулов. Однако это также увеличивает сложность маршрутизации по мере увеличения количества пулов (возможно, UniswapX призван решить эту проблему).
UniswapV4 явно делает акцент на расширении всей экосистемы Uniswap, превращая ее в инфраструктуру, позволяющую создавать больше сервисов на основе пулов Uniswap. Это помогает повысить конкурентоспособность Uniswap и снижает риск, связанный с альтернативными услугами. Однако достигнет ли он ожидаемого успеха, еще предстоит выяснить. Среди основных моментов - сочетание Flash Accounting и EIP1153, и мы уверены, что в будущем все больше сервисов будут использовать эти функции, что приведет к появлению различных сценариев применения. Это основная концепция UniswapV4, и мы надеемся, что она дает более глубокое понимание того, как работает UniswapV4. Если в статье есть ошибки, пожалуйста, не стесняйтесь указывать на них. Мы также приветствуем обсуждения и отзывы.
Наконец, мы хотели бы поблагодарить Антона Ченга и Пин Чена за рецензирование статьи и ценные замечания!
Пригласить больше голосов
С момента анонса UniswapV4 эта своп-платформа претерпела значительные изменения, превратившись из простой своп-платформы в поставщика инфраструктурных услуг. В частности, широкое внимание привлекла функция Hooks в V4. Проведя глубокое исследование, я собрал некоторые материалы, чтобы помочь всем лучше понять эту трансформацию и ее реализацию.
Инновации UniswapV4 направлены не только на улучшение технологии AMM, но и на расширение экосистемы. В частности, эта инновация включает в себя следующие ключевые особенности:
В следующих разделах я подробно расскажу о значении этих функций и принципах их реализации.
source: https://twitter.com/jermywkh/status/1670779830621851650
В UniswapV4 используется метод учета, похожий на метод двойной записи, для отслеживания изменений баланса токенов, соответствующих каждой операции. Этот метод двойной записи требует одновременного отражения каждой операции на нескольких счетах и обеспечения баланса активов между этими счетами. Например, предположим, что пользователь обменивает 100 TokenA на 50 TokenB из пула. Запись в бухгалтерской книге будет выглядеть следующим образом:
В UniswapV4 этот метод ведения записей используется в основном для основных операций, и переменная хранения с именем lockState.currencyDelta[currency] используется в коде для записи суммы изменений баланса токенов. Если значение этой дельты положительное, оно представляет собой ожидаемое увеличение количества токенов в пуле, в то время как отрицательное значение представляет собой ожидаемое уменьшение количества токенов. Как вариант, если значение положительное, оно указывает на недостаток токенов в пуле (ожидаемая сумма для получения), а отрицательное значение указывает на избыток токенов в пуле (ожидаемая сумма для снятия пользователями). В следующем списке показано влияние различных операций на Token Delta:
Среди этих операций только "settle" и "take" связаны с фактической передачей токенов, в то время как остальные операции отвечают только за обновление значения TokenDelta.
Здесь мы используем простой пример, чтобы проиллюстрировать, как обновить TokenDelta. Предположим, что сегодня мы обменяли 100 TokenA на 50 TokenB:
Когда вся операция обмена завершена, TokenADelta и TokenBDelta обнуляются до 0. Это означает, что операция была полностью сбалансирована, что обеспечивает постоянство баланса счетов.
Ранее упоминалось, что UniswapV4 использует переменные хранения для записи TokenDelta. Однако в рамках контракта чтение и запись в переменные хранения стоят довольно дорого. Это подводит нас к еще одному EIP, представленному Uniswap: EIP1153 - опкоды переходного хранения.
UniswapV4 планирует использовать опкоды TSTORE и TLOAD, предоставленные EIP1153, для обновления TokenDelta. Переменные хранения, использующие опкоды переходного хранения, будут отбрасываться после завершения транзакции (аналогично переменным памяти), что позволит снизить плату за газ.
Было подтверждено, что EIP1153 будет включен в предстоящее обновление Cancun, и UniswapV4 также заявил, что он появится после обновления Cancun, как сообщается здесь.
источник: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
В UniswapV4 появился механизм блокировки, который означает, что перед выполнением любой операции с пулом Вы должны сначала вызвать PoolManager.lock(), чтобы получить блокировку. Во время выполнения lock() проверяется, равно ли значение TokenDelta 0, в противном случае произойдет возврат. После того, как PoolManager.lock() успешно получен, он вызывает функцию lockAcquired() в msg.sender. Внутри функции lockAcquired() выполняются операции, связанные с пулом, такие как swap и modifyPosition.
Этот процесс показан ниже. Когда пользователю необходимо выполнить операцию обмена токенов, он должен вызвать смарт-контракт с функцией lockAcquired() (именуемый Контрактом обратного вызова). Контракт обратного вызова сначала вызывает PoolManager.lock(), а затем PoolManager вызывает функцию lockAcquired() контракта обратного вызова. Внутри функции lockAcquired() определяется логика, связанная с операциями Pool, такими как swap, settle и take. Наконец, когда блокировка() подходит к концу, PoolManager проверяет, была ли TokenDelta, связанная с этой операцией, сброшена в 0, гарантируя, что баланс активов в Пуле останется нетронутым.
Singleton Contract означает, что UniswapV4 отказался от предыдущей модели Factory-Pool. Каждый пул больше не является независимым смарт-контрактом, но все пулы используют один единственный контракт. Такая конструкция, в сочетании с механизмом Flash Accounting, требует только обновления необходимых переменных хранения, что еще больше снижает сложность и стоимость операций.
В приведенном ниже примере с использованием UniswapV3 обмен ETH на DAI потребует как минимум четыре передачи токенов (операции записи в хранилище). Сюда входят многочисленные изменения, зафиксированные для токенов USDC, USDT и DAI. Однако благодаря усовершенствованиям в UniswapV4 в сочетании с механизмом Flash Accounting требуется только одна передача токена (перемещение DAI от пула к пользователю), что значительно сокращает количество операций и расходы.
source: https://twitter.com/Uniswap/status/1671208668304486404
В последнем обновлении UniswapV4 наиболее заметной особенностью является архитектура крючков. Это обновление обеспечивает большую гибкость в плане доступности Пула. Крючки - это дополнительные действия, которые запускаются через Контракт крючков при выполнении определенных операций с Пулом. Эти действия делятся на initialize (создание пула), modifyPosition (добавление/удаление ликвидности), swap и donate. В каждой категории есть действия до и после выполнения.
Такая конструкция позволяет пользователям выполнять пользовательскую логику до и после определенных операций, что делает ее более гибкой и расширяет функциональность 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
После того, как пользователь завершит обмен токенами в этом пуле, пул вызовет функцию afterSwap() контракта Hook. Основная логика afterSwap заключается в том, чтобы удалить ликвидность ранее размещенных ордеров, которые были исполнены между предыдущим и текущим ценовым диапазоном. Такое поведение эквивалентно тому, что заказ будет выполнен.
источник: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
Вот блок-схема, иллюстрирующая процесс выполнения лимитного ордера:
Выше описан весь процесс реализации Limit-Order с помощью механизма Hook.
У Хукса есть несколько интересных моментов, которыми, на мой взгляд, стоит поделиться.
Решение о выполнении определенных операций до/после определяется крайним левым 1 байтом адреса контракта Hook. 1 байт равен 8 битам, что соответствует 8 дополнительным действиям. Пул будет проверять, равен ли бит этого действия 1, чтобы определить, вызывать ли соответствующую функцию крючка контракта Hook. Это также означает, что адрес контракта Hook должен быть разработан особым образом и не может быть произвольно выбран в качестве контракта Hook. Эта конструкция в основном направлена на снижение потребления газа и перенос затрат на контрактное развертывание для достижения более эффективной работы. (PS: На практике различные соли CREATE2 могут быть использованы для перебора адресов контрактов, удовлетворяющих условиям)
Помимо возможности выполнять дополнительные операции до и после каждого действия, крючки также поддерживают реализацию динамических сборов. При создании пула Вы можете указать, нужно ли включить динамическую оплату. Если динамические комиссии включены, при обмене токенов вызывается функция getFee() контракта Hook. Контракт с Крюком может определять размер взимаемой платы в зависимости от текущего состояния Пула. Такая конструкция позволяет гибко рассчитывать плату в зависимости от фактических обстоятельств, что повышает гибкость системы.
Каждый пул должен определить контракт с крючком во время своего создания, и он не может быть изменен после этого (хотя разные пулы могут использовать один и тот же контракт с крючком). Это происходит потому, что крючки считаются частью PoolKey, а PoolManager использует PoolKey, чтобы определить, с каким пулом работать. Даже если активы одинаковы, если договор Hook отличается, он будет считаться другим Pool. Такая конструкция позволяет управлять состоянием и операциями разных пулов независимо друг от друга, обеспечивая согласованность пулов. Однако это также увеличивает сложность маршрутизации по мере увеличения количества пулов (возможно, UniswapX призван решить эту проблему).
UniswapV4 явно делает акцент на расширении всей экосистемы Uniswap, превращая ее в инфраструктуру, позволяющую создавать больше сервисов на основе пулов Uniswap. Это помогает повысить конкурентоспособность Uniswap и снижает риск, связанный с альтернативными услугами. Однако достигнет ли он ожидаемого успеха, еще предстоит выяснить. Среди основных моментов - сочетание Flash Accounting и EIP1153, и мы уверены, что в будущем все больше сервисов будут использовать эти функции, что приведет к появлению различных сценариев применения. Это основная концепция UniswapV4, и мы надеемся, что она дает более глубокое понимание того, как работает UniswapV4. Если в статье есть ошибки, пожалуйста, не стесняйтесь указывать на них. Мы также приветствуем обсуждения и отзывы.
Наконец, мы хотели бы поблагодарить Антона Ченга и Пин Чена за рецензирование статьи и ценные замечания!