UniswapV4の発表以来、このスワッププラットフォームは大きな変革を遂げ、単純なスワッププラットフォームからインフラストラクチャサービスプロバイダーへと進化しました。 特に、V4 のフック機能は広く注目を集めています。 綿密な調査の後、この変換とその実装を誰もがよりよく理解できるように、いくつかのコンテンツをまとめました。
UniswapV4のイノベーションの焦点は、AMM技術の向上だけでなく、エコシステムの拡大でもあります。 具体的には、このイノベーションには次の主要な機能が含まれています。
次のセクションでは、これらの機能の重要性とその実装原則について詳しく説明します。
出典: https://twitter.com/jermywkh/status/1670779830621851650
UniswapV4は、複式簿記に似た記録管理方法を採用しており、各操作に対応するトークンの残高変化を追跡します。 この複式簿記の方法では、各取引を複数の口座に同時に記録し、これらの口座間の資産のバランスが保たれていることを確認する必要があります。 たとえば、ユーザーがプールから 100 TokenA を 50 TokenB と交換したとします。 台帳のレコードは次のようになります。
UniswapV4では、この記録保持方法は主に主要な操作に使用され、lockState.currencyDelta[currency] は、トークン残高の変化量を記録するためにコードで使用されます。 このデルタの値が正の場合、プール内のトークン量の予想される増加を表し、負の値はトークン量の予想される減少を表します。 また、値が正の場合は、プール内のトークン不足量(受信予定量)を示し、負の値はプール内のトークン不足量(ユーザーが引き出す予定の予想量)を示します。 次の一覧は、トークンデルタに対するさまざまな操作の影響を示しています。
これらの操作のうち、トークンの実際の転送は「決済」と「取得」のみで、他の操作はTokenDelta値の更新のみを担当します。
ここでは、簡単な例を使用して、TokenDeltaを更新する方法を説明します。 今日、100 TokenA を 50 TokenB と交換したとします。
交換操作全体が完了すると、TokenADelta と TokenBDelta の両方が 0 にリセットされます。これは、操作が完全にバランスが取れていることを意味し、口座残高の一貫性が確保されます。
以前、UniswapV4はストレージ変数を利用してTokenDeltaを記録すると述べました。 ただし、コントラクト内では、ストレージ変数の読み取りと書き込みにかなりのコストがかかります。 そこで、Uniswapが導入したもう一つのEIP、 EIP1153 - Transient Storage Opcodesについて考えてみました。
UniswapV4は、 EIP1153 が提供するTSTOREおよびTLOADオペコードを使用してTokenDeltaを更新する予定です。 Transient Storage Opcodeを採用するストレージ変数は、トランザクションの終了後に破棄されるため(メモリ変数と同様)、ガス料金が削減されます。
EIP1153は今後の カンクンのアップグレードに含まれることが確認されており、 UniswapV4はカンクンのアップグレード後に稼働するとも述べています。
出典: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
UniswapV4 ではロックメカニズムが導入されており、Pool 操作を実行する前に、まず PoolManager.lock() を呼び出してロックを取得する必要があります。 lock() の実行中に、TokenDelta 値が 0 かどうかをチェックし、そうでない場合は元に戻します。 PoolManager.lock()が正常に取得されると、msg.senderのlockAcquired()関数が呼び出されます。 lockAcquired() 関数内では、swap や modifyPosition など、プールに関連する操作が実行されます。
このプロセスを以下に示します。 ユーザーがトークンスワップ操作を実行する必要がある場合は、lockAcquired()関数(コールバックコントラクトと呼ばれる)を使用してスマートコントラクトを呼び出す必要があります。 コールバック コントラクトは、最初に PoolManager.lock() を呼び出します。 次に、PoolManager はコールバック コントラクトの lockAcquired() 関数を呼び出します。 lockAcquired() 関数内では、スワップ、決済、テイクなどのプール操作に関連するロジックが定義されています。 最後に、lock() が終了に近づくと、PoolManager は、この操作に関連付けられた TokenDelta が 0 にリセットされているかどうかをチェックし、プール内の資産のバランスが損なわれていないことを確認します。
シングルトンコントラクトとは、UniswapV4が以前のファクトリプールモデルを放棄したことを意味します。 各プールは独立したスマートコントラクトではなくなりましたが、すべてのプールは単一のシングルトンコントラクトを共有します。 この設計は、フラッシュアカウンティングメカニズムと組み合わせることで、必要なストレージ変数を更新するだけで済むため、運用の複雑さとコストがさらに削減されます。
以下の例では、UniswapV3を例にとると、ETHをDAIに交換するには、少なくとも4つのトークン転送(ストレージ書き込み操作)が必要になります。 これには、USDC、USDT、およびDAIトークンに記録された複数の変更が含まれます。 しかし、UniswapV4の改良とフラッシュアカウンティングの仕組みにより、必要なトークンの転送は1回(DAIをプールからユーザーに移動する)のみで済むため、操作回数とコストが大幅に削減されます。
出典: https://twitter.com/Uniswap/status/1671208668304486404
UniswapV4の最新アップデートで最も注目すべき機能は、フックアーキテクチャです。 この更新により、プールの可用性に関して大きな柔軟性がもたらされます。 フックは、プールで特定の操作を実行するときにフック コントラクトを介してトリガーされる追加のアクションです。 これらのアクションは、初期化(プールの作成)、modifyPosition(流動性の追加/削除)、スワップ、および寄付に分類されます。 各カテゴリには、実行前と実行後のアクションがあります。
この設計により、ユーザーは特定の操作の前後にカスタムロジックを実行できるため、UniswapV4の柔軟性と機能が拡張されます。
出典: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf
次に、指値注文を例に、UniswapV4におけるHooksの実際の操作工程を解説します。 始める前に、UniswapV4で指値注文を実装する原則を簡単に説明しましょう。
指値注文のUniswapV4実装は、特定の価格帯に流動性を追加し、その範囲の流動性がスワップされた場合に流動性の削除操作を実行することで機能します。
例えば、ETHに1900〜2000年の価格帯で流動性を追加し、ETHの価格が1800年から2100年に上昇したとします。 この時点で、1900-2000年の価格帯で以前に追加したETHの流動性はすべてUSDCにスワップされています(ETH-USDCプールで想定)。 この時点で流動性を取り除くことで、現在の価格帯である1900-2000でETH成行注文を実行するのと同様の効果を得ることができます。
この例は、UniswapV4の GitHub から取得しています。 この例では、指値注文フック契約は、afterInitialize と afterSwap の 2 つのフックを提供します。 afterInitializeフックは、誰かがスワップした後にどの指値注文が一致したかを判断するために、プールを作成するときに価格帯(ティック)を記録するために使用されます。
ユーザーが注文を出す必要がある場合、フック契約はユーザーが指定した価格帯と数量に基づいて流動性追加操作を実行します。 指値注文のフック契約では、 place() 関数を見ることができます。 主なロジックは、ロックを取得した後に lockAcquiredPlace() 関数を呼び出して流動性追加操作を実行することであり、これは指値注文を出すことと同等です。
出典: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L246
ユーザーがこのプール内でスワップトークンを完了すると、プールはフックコントラクトの afterSwap() 関数を呼び出します。 afterSwapの主なロジックは、以前の価格帯と現在の価格帯の間に実行された以前に出された注文の流動性を削除することです。 この動作は、注文が約定するのと同じです。
出典: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
以下は、指値注文を実行するプロセスを示すフローチャートです。
上記は、フックメカニズムを使用して指値注文を実装するプロセス全体です。
フックには、共有する価値があると思ういくつかの興味深いポイントがあります。
前後に特定の操作を実行するかどうかの決定は、フックコントラクトアドレスの左端の1バイトによって決定されます。1 バイトは 8 ビットに相当し、8 つの追加アクションに相当します。 プールは、そのアクションのビットが 1 であるかどうかをチェックして、フック コントラクトの対応するフック関数を呼び出すかどうかを判断します。 これは、フックコントラクトのアドレスを特定の方法で設計する必要があり、フックコントラクトとして任意に選択できないことも意味します。 この設計は、主にガス消費量を削減し、コストを契約展開にシフトして、より効率的な運用を実現することを目的としています。 (追記:実際には、異なる CREATE2 塩を使用して、条件を満たすコントラクトアドレスをブルートフォースで計算できます)
フックは、各アクションの前後に追加の操作を実行できるだけでなく、動的な手数料の実装もサポートしています。 プールを作成する際に、動的手数料を有効にするかどうかを指定できます。 動的手数料が有効な場合、トークンを交換するときにHookコントラクトのgetFee()関数が呼び出されます。 フックコントラクトは、プールの現在の状態に基づいて請求される手数料の金額を決定できます。 この設計により、実際の状況に基づいた柔軟な料金計算が可能になり、システムの柔軟性が高まります。
各プールは、作成時にフックコントラクトを決定する必要があり、後で変更することはできません(ただし、異なるプールが同じフックコントラクトを共有できます)。 これは主に、フックが PoolKey の一部と見なされ、PoolManager が PoolKey を使用して操作するプールを識別するためです。 アセットが同じでも、フックコントラクトが異なる場合は、別のプールとして扱われます。 この設計により、異なるプールの状態と操作を個別に管理でき、プールの一貫性が確保されます。 ただし、プールの数が増えるにつれて、ルーティングの複雑さも増します(おそらくUniswapXはこの問題を解決するように設計されています)。
UniswapV4は、Uniswapエコシステム全体を拡大し、Uniswapプールを基盤により多くのサービスを構築できるようにするためのインフラに変えることを明確に強調しています。 これにより、Uniswapの競争力が高まり、代替サービスのリスクが軽減されます。 しかし、期待通りの成功を収めるかどうかは、まだわかりません。 「フラッシュアカウンティング」と「EIP1153」の組み合わせが目玉で、今後、これらの機能を採用するサービスが増え、さまざまな活用シーンが生まれると考えています。 これがUniswapV4のコアコンセプトであり、UniswapV4がどのように動作するかをより深く理解できることを願っています。 記事に誤りがある場合は、遠慮なくご指摘ください。 また、議論やフィードバックも歓迎します。
最後に、記事をレビューし、貴重なフィードバックを提供してくれた Anton Cheng 氏と Ping Chen 氏に感謝します。
UniswapV4の発表以来、このスワッププラットフォームは大きな変革を遂げ、単純なスワッププラットフォームからインフラストラクチャサービスプロバイダーへと進化しました。 特に、V4 のフック機能は広く注目を集めています。 綿密な調査の後、この変換とその実装を誰もがよりよく理解できるように、いくつかのコンテンツをまとめました。
UniswapV4のイノベーションの焦点は、AMM技術の向上だけでなく、エコシステムの拡大でもあります。 具体的には、このイノベーションには次の主要な機能が含まれています。
次のセクションでは、これらの機能の重要性とその実装原則について詳しく説明します。
出典: https://twitter.com/jermywkh/status/1670779830621851650
UniswapV4は、複式簿記に似た記録管理方法を採用しており、各操作に対応するトークンの残高変化を追跡します。 この複式簿記の方法では、各取引を複数の口座に同時に記録し、これらの口座間の資産のバランスが保たれていることを確認する必要があります。 たとえば、ユーザーがプールから 100 TokenA を 50 TokenB と交換したとします。 台帳のレコードは次のようになります。
UniswapV4では、この記録保持方法は主に主要な操作に使用され、lockState.currencyDelta[currency] は、トークン残高の変化量を記録するためにコードで使用されます。 このデルタの値が正の場合、プール内のトークン量の予想される増加を表し、負の値はトークン量の予想される減少を表します。 また、値が正の場合は、プール内のトークン不足量(受信予定量)を示し、負の値はプール内のトークン不足量(ユーザーが引き出す予定の予想量)を示します。 次の一覧は、トークンデルタに対するさまざまな操作の影響を示しています。
これらの操作のうち、トークンの実際の転送は「決済」と「取得」のみで、他の操作はTokenDelta値の更新のみを担当します。
ここでは、簡単な例を使用して、TokenDeltaを更新する方法を説明します。 今日、100 TokenA を 50 TokenB と交換したとします。
交換操作全体が完了すると、TokenADelta と TokenBDelta の両方が 0 にリセットされます。これは、操作が完全にバランスが取れていることを意味し、口座残高の一貫性が確保されます。
以前、UniswapV4はストレージ変数を利用してTokenDeltaを記録すると述べました。 ただし、コントラクト内では、ストレージ変数の読み取りと書き込みにかなりのコストがかかります。 そこで、Uniswapが導入したもう一つのEIP、 EIP1153 - Transient Storage Opcodesについて考えてみました。
UniswapV4は、 EIP1153 が提供するTSTOREおよびTLOADオペコードを使用してTokenDeltaを更新する予定です。 Transient Storage Opcodeを採用するストレージ変数は、トランザクションの終了後に破棄されるため(メモリ変数と同様)、ガス料金が削減されます。
EIP1153は今後の カンクンのアップグレードに含まれることが確認されており、 UniswapV4はカンクンのアップグレード後に稼働するとも述べています。
出典: https://etherworld.co/2022/12/13/transient-storage-for-beginners/
UniswapV4 ではロックメカニズムが導入されており、Pool 操作を実行する前に、まず PoolManager.lock() を呼び出してロックを取得する必要があります。 lock() の実行中に、TokenDelta 値が 0 かどうかをチェックし、そうでない場合は元に戻します。 PoolManager.lock()が正常に取得されると、msg.senderのlockAcquired()関数が呼び出されます。 lockAcquired() 関数内では、swap や modifyPosition など、プールに関連する操作が実行されます。
このプロセスを以下に示します。 ユーザーがトークンスワップ操作を実行する必要がある場合は、lockAcquired()関数(コールバックコントラクトと呼ばれる)を使用してスマートコントラクトを呼び出す必要があります。 コールバック コントラクトは、最初に PoolManager.lock() を呼び出します。 次に、PoolManager はコールバック コントラクトの lockAcquired() 関数を呼び出します。 lockAcquired() 関数内では、スワップ、決済、テイクなどのプール操作に関連するロジックが定義されています。 最後に、lock() が終了に近づくと、PoolManager は、この操作に関連付けられた TokenDelta が 0 にリセットされているかどうかをチェックし、プール内の資産のバランスが損なわれていないことを確認します。
シングルトンコントラクトとは、UniswapV4が以前のファクトリプールモデルを放棄したことを意味します。 各プールは独立したスマートコントラクトではなくなりましたが、すべてのプールは単一のシングルトンコントラクトを共有します。 この設計は、フラッシュアカウンティングメカニズムと組み合わせることで、必要なストレージ変数を更新するだけで済むため、運用の複雑さとコストがさらに削減されます。
以下の例では、UniswapV3を例にとると、ETHをDAIに交換するには、少なくとも4つのトークン転送(ストレージ書き込み操作)が必要になります。 これには、USDC、USDT、およびDAIトークンに記録された複数の変更が含まれます。 しかし、UniswapV4の改良とフラッシュアカウンティングの仕組みにより、必要なトークンの転送は1回(DAIをプールからユーザーに移動する)のみで済むため、操作回数とコストが大幅に削減されます。
出典: https://twitter.com/Uniswap/status/1671208668304486404
UniswapV4の最新アップデートで最も注目すべき機能は、フックアーキテクチャです。 この更新により、プールの可用性に関して大きな柔軟性がもたらされます。 フックは、プールで特定の操作を実行するときにフック コントラクトを介してトリガーされる追加のアクションです。 これらのアクションは、初期化(プールの作成)、modifyPosition(流動性の追加/削除)、スワップ、および寄付に分類されます。 各カテゴリには、実行前と実行後のアクションがあります。
この設計により、ユーザーは特定の操作の前後にカスタムロジックを実行できるため、UniswapV4の柔軟性と機能が拡張されます。
出典: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf
次に、指値注文を例に、UniswapV4におけるHooksの実際の操作工程を解説します。 始める前に、UniswapV4で指値注文を実装する原則を簡単に説明しましょう。
指値注文のUniswapV4実装は、特定の価格帯に流動性を追加し、その範囲の流動性がスワップされた場合に流動性の削除操作を実行することで機能します。
例えば、ETHに1900〜2000年の価格帯で流動性を追加し、ETHの価格が1800年から2100年に上昇したとします。 この時点で、1900-2000年の価格帯で以前に追加したETHの流動性はすべてUSDCにスワップされています(ETH-USDCプールで想定)。 この時点で流動性を取り除くことで、現在の価格帯である1900-2000でETH成行注文を実行するのと同様の効果を得ることができます。
この例は、UniswapV4の GitHub から取得しています。 この例では、指値注文フック契約は、afterInitialize と afterSwap の 2 つのフックを提供します。 afterInitializeフックは、誰かがスワップした後にどの指値注文が一致したかを判断するために、プールを作成するときに価格帯(ティック)を記録するために使用されます。
ユーザーが注文を出す必要がある場合、フック契約はユーザーが指定した価格帯と数量に基づいて流動性追加操作を実行します。 指値注文のフック契約では、 place() 関数を見ることができます。 主なロジックは、ロックを取得した後に lockAcquiredPlace() 関数を呼び出して流動性追加操作を実行することであり、これは指値注文を出すことと同等です。
出典: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L246
ユーザーがこのプール内でスワップトークンを完了すると、プールはフックコントラクトの afterSwap() 関数を呼び出します。 afterSwapの主なロジックは、以前の価格帯と現在の価格帯の間に実行された以前に出された注文の流動性を削除することです。 この動作は、注文が約定するのと同じです。
出典: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192
以下は、指値注文を実行するプロセスを示すフローチャートです。
上記は、フックメカニズムを使用して指値注文を実装するプロセス全体です。
フックには、共有する価値があると思ういくつかの興味深いポイントがあります。
前後に特定の操作を実行するかどうかの決定は、フックコントラクトアドレスの左端の1バイトによって決定されます。1 バイトは 8 ビットに相当し、8 つの追加アクションに相当します。 プールは、そのアクションのビットが 1 であるかどうかをチェックして、フック コントラクトの対応するフック関数を呼び出すかどうかを判断します。 これは、フックコントラクトのアドレスを特定の方法で設計する必要があり、フックコントラクトとして任意に選択できないことも意味します。 この設計は、主にガス消費量を削減し、コストを契約展開にシフトして、より効率的な運用を実現することを目的としています。 (追記:実際には、異なる CREATE2 塩を使用して、条件を満たすコントラクトアドレスをブルートフォースで計算できます)
フックは、各アクションの前後に追加の操作を実行できるだけでなく、動的な手数料の実装もサポートしています。 プールを作成する際に、動的手数料を有効にするかどうかを指定できます。 動的手数料が有効な場合、トークンを交換するときにHookコントラクトのgetFee()関数が呼び出されます。 フックコントラクトは、プールの現在の状態に基づいて請求される手数料の金額を決定できます。 この設計により、実際の状況に基づいた柔軟な料金計算が可能になり、システムの柔軟性が高まります。
各プールは、作成時にフックコントラクトを決定する必要があり、後で変更することはできません(ただし、異なるプールが同じフックコントラクトを共有できます)。 これは主に、フックが PoolKey の一部と見なされ、PoolManager が PoolKey を使用して操作するプールを識別するためです。 アセットが同じでも、フックコントラクトが異なる場合は、別のプールとして扱われます。 この設計により、異なるプールの状態と操作を個別に管理でき、プールの一貫性が確保されます。 ただし、プールの数が増えるにつれて、ルーティングの複雑さも増します(おそらくUniswapXはこの問題を解決するように設計されています)。
UniswapV4は、Uniswapエコシステム全体を拡大し、Uniswapプールを基盤により多くのサービスを構築できるようにするためのインフラに変えることを明確に強調しています。 これにより、Uniswapの競争力が高まり、代替サービスのリスクが軽減されます。 しかし、期待通りの成功を収めるかどうかは、まだわかりません。 「フラッシュアカウンティング」と「EIP1153」の組み合わせが目玉で、今後、これらの機能を採用するサービスが増え、さまざまな活用シーンが生まれると考えています。 これがUniswapV4のコアコンセプトであり、UniswapV4がどのように動作するかをより深く理解できることを願っています。 記事に誤りがある場合は、遠慮なくご指摘ください。 また、議論やフィードバックも歓迎します。
最後に、記事をレビューし、貴重なフィードバックを提供してくれた Anton Cheng 氏と Ping Chen 氏に感謝します。