UniswapV4のコアメカニズムを探る

上級Dec 24, 2023
この記事では、UniswapV4の3つの革新的な機能(Flash Accounting、Singleton Contract、Hooks Architecture)をコードと実装の観点から解説します。
UniswapV4のコアメカニズムを探る

はじめに:

UniswapV4の発表以来、このスワッププラットフォームは大きな変革を遂げ、単純なスワッププラットフォームからインフラストラクチャサービスプロバイダーへと進化しました。 特に、V4 のフック機能は広く注目を集めています。 綿密な調査の後、この変換とその実装を誰もがよりよく理解できるように、いくつかのコンテンツをまとめました。

UniswapV4のイノベーションの焦点は、AMM技術の向上だけでなく、エコシステムの拡大でもあります。 具体的には、このイノベーションには次の主要な機能が含まれています。

  • フラッシュアカウンティング
  • シングルトン契約
  • フックのアーキテクチャ

次のセクションでは、これらの機能の重要性とその実装原則について詳しく説明します。

出典: https://twitter.com/jermywkh/status/1670779830621851650

フラッシュアカウンティング

複式簿記

UniswapV4は、複式簿記に似た記録管理方法を採用しており、各操作に対応するトークンの残高変化を追跡します。 この複式簿記の方法では、各取引を複数の口座に同時に記録し、これらの口座間の資産のバランスが保たれていることを確認する必要があります。 たとえば、ユーザーがプールから 100 TokenA を 50 TokenB と交換したとします。 台帳のレコードは次のようになります。

  • USER: TokenA が 100 ユニット減少 (-100)、TokenB が 50 ユニット増加 (+50) しました。
  • プール:トークンAが100ユニット増加(+100)、トークンBが50ユニット減少(-50)。

トークンデルタと関連操作

UniswapV4では、この記録保持方法は主に主要な操作に使用され、lockState.currencyDelta[currency] は、トークン残高の変化量を記録するためにコードで使用されます。 このデルタの値が正の場合、プール内のトークン量の予想される増加を表し、負の値はトークン量の予想される減少を表します。 また、値が正の場合は、プール内のトークン不足量(受信予定量)を示し、負の値はプール内のトークン不足量(ユーザーが引き出す予定の予想量)を示します。 次の一覧は、トークンデルタに対するさまざまな操作の影響を示しています。

  • modifyPosition: 流動性の追加/削除操作を表します。 [流動性の追加] では、TokenDelta は加算 (プールに追加される TokenA の予想される量を表す) を使用して更新されます。 [Remove liquidity] では、減算 (プールから引き出される TokenB の予想量を表す) を使用して TokenDelta が更新されます。
  • swap: スワップ操作を表します。 TokenA を TokenB と入れ替える例をとると、TokenADelta は加算を使用して更新され、TokenBDelta は減算を使用して更新されます。
  • settle:プールへのトークンの転送に付随します。 プールは、前後のトークン量の増加を計算し、減算を使用して TokenDelta を更新します。 プールが期待された量のトークンを受け取った場合、減算によって TokenDelta がゼロになります。
  • take:プールからのトークンの引き出しに付随します。 TokenDelta は加算を使用して更新され、トークンがプールから削除されたことを示します。
  • mint:TokenDeltaを更新する動作は「take」と似ていますが、実際にプールからトークンを引き出すのではなく、トークンがプールに残っている間、引き出しの証拠としてERC1155トークンが発行されます。 後で、ユーザーはERC1155トークンを書き込むことで、プールからトークンを取得できます。 このアプローチの目的は2つあります:1. ERC20トークン転送のガスコストを節約する(コントラクトコール+ストレージ書き込みを1回減らす)、および将来的にERC1155トークンバーンを使用してトランザクションのTokenDeltaを更新する。 2.プール内の流動性を維持して、ユーザースワッピングエクスペリエンスを向上させるために深い流動性プールを維持します。
  • donate: この操作は、プールへのトークンの寄付を宣言しますが、実際には、トークンは「決済」を使用してプールに転送する必要があります。 したがって、この場合、TokenDelta は加算を使用して更新されます。

これらの操作のうち、トークンの実際の転送は「決済」と「取得」のみで、他の操作はTokenDelta値の更新のみを担当します。

トークンデルタの例

ここでは、簡単な例を使用して、TokenDeltaを更新する方法を説明します。 今日、100 TokenA を 50 TokenB と交換したとします。

  1. トランザクションが開始される前は、TokenADelta と TokenBDelta はどちらも 0 です。
  2. swap: プールが受け取る必要のある TokenA の量と、ユーザーが受け取る TokenB の量を計算します。 この時点で、TokenADelta = 100、TokenBDelta = -50 です。
  3. settle: 100 TokenA をプールに送り、TokenADelta = 100 - 100 = 0 を更新します。
  4. take: プールからユーザーのアカウントに 50 TokenB を転送し、TokenBDelta = -50 + 50 = 0 を更新します。
  5. トランザクションが完了すると、TokenADelta と TokenBDelta の両方が 0 になります。

交換操作全体が完了すると、TokenADelta と TokenBDelta の両方が 0 にリセットされます。これは、操作が完全にバランスが取れていることを意味し、口座残高の一貫性が確保されます。

EIP-1153:一時ストレージオペコード

以前、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(流動性の追加/削除)、スワップ、および寄付に分類されます。 各カテゴリには、実行前と実行後のアクションがあります。

  • beforeInitialize / afterInitialize (初期化後)
  • beforeModifyPosition / afterModifyPosition (変更前位置 / 変更後位置)
  • beforeSwap / afterSwap (前スワップ) / 後スワップ
  • beforeDonate / afterDonate

この設計により、ユーザーは特定の操作の前後にカスタムロジックを実行できるため、UniswapV4の柔軟性と機能が拡張されます。

出典: https://github.com/Uniswap/v4-core/blob/main/whitepaper-v4-draft.pdf

フックの例 — 指値注文フック

次に、指値注文を例に、UniswapV4におけるHooksの実際の操作工程を解説します。 始める前に、UniswapV4で指値注文を実装する原則を簡単に説明しましょう。

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() 関数を呼び出します。 afterSwapの主なロジックは、以前の価格帯と現在の価格帯の間に実行された以前に出された注文の流動性を削除することです。 この動作は、注文が約定するのと同じです。

出典: https://github.com/Uniswap/v4-periphery/blob/main/contracts/hooks/examples/LimitOrder.sol#L192

指値注文フロー

以下は、指値注文を実行するプロセスを示すフローチャートです。

  1. 注文発注者は、注文をフック契約に送信します。
  2. フックコントラクトは、注文情報に基づいて流動性追加操作を実行します。
  3. 通常のユーザーは、プールでトークンスワップ操作を実行します。
  4. スワップトークン操作の完了後、プールはフックコントラクトのafterSwap()関数を呼び出します。
  5. フック契約は、スワップされたトークンの価格帯の変動に基づいて、約定した指値注文の流動性除去操作を実行します。

上記は、フックメカニズムを使用して指値注文を実装するプロセス全体です。

フック:その他の機能

フックには、共有する価値があると思ういくつかの興味深いポイントがあります。

フック コントラクト アドレス ビット

前後に特定の操作を実行するかどうかの決定は、フックコントラクトアドレスの左端の1バイトによって決定されます。1 バイトは 8 ビットに相当し、8 つの追加アクションに相当します。 プールは、そのアクションのビットが 1 であるかどうかをチェックして、フック コントラクトの対応するフック関数を呼び出すかどうかを判断します。 これは、フックコントラクトのアドレスを特定の方法で設計する必要があり、フックコントラクトとして任意に選択できないことも意味します。 この設計は、主にガス消費量を削減し、コストを契約展開にシフトして、より効率的な運用を実現することを目的としています。 (追記:実際には、異なる CREATE2 塩を使用して、条件を満たすコントラクトアドレスをブルートフォースで計算できます)

ダイナミックフィー

フックは、各アクションの前後に追加の操作を実行できるだけでなく、動的な手数料の実装もサポートしています。 プールを作成する際に、動的手数料を有効にするかどうかを指定できます。 動的手数料が有効な場合、トークンを交換するときにHookコントラクトのgetFee()関数が呼び出されます。 フックコントラクトは、プールの現在の状態に基づいて請求される手数料の金額を決定できます。 この設計により、実際の状況に基づいた柔軟な料金計算が可能になり、システムの柔軟性が高まります。

プールの作成

各プールは、作成時にフックコントラクトを決定する必要があり、後で変更することはできません(ただし、異なるプールが同じフックコントラクトを共有できます)。 これは主に、フックが PoolKey の一部と見なされ、PoolManager が PoolKey を使用して操作するプールを識別するためです。 アセットが同じでも、フックコントラクトが異なる場合は、別のプールとして扱われます。 この設計により、異なるプールの状態と操作を個別に管理でき、プールの一貫性が確保されます。 ただし、プールの数が増えるにつれて、ルーティングの複雑さも増します(おそらくUniswapXはこの問題を解決するように設計されています)。

TLの;博士

  • フラッシュアカウンティングは、各トークンの数量変更を追跡し、トランザクションの完了後にすべての変更がゼロになるようにするために使用されます。 ガス代を節約するために、フラッシュアカウンティングはEIP1153が提供する特別な保管方法を使用します。
  • シングルトン コントラクトの設計は、複数のストレージ変数の更新を回避することで、ガス消費量を削減するのに役立ちます。
  • フックアーキテクチャは、実行前と実行後のステージに分かれた追加の操作を提供します。 これにより、各プール操作の柔軟性が高まりますが、プールのルーティングがより複雑になります。

UniswapV4は、Uniswapエコシステム全体を拡大し、Uniswapプールを基盤により多くのサービスを構築できるようにするためのインフラに変えることを明確に強調しています。 これにより、Uniswapの競争力が高まり、代替サービスのリスクが軽減されます。 しかし、期待通りの成功を収めるかどうかは、まだわかりません。 「フラッシュアカウンティング」と「EIP1153」の組み合わせが目玉で、今後、これらの機能を採用するサービスが増え、さまざまな活用シーンが生まれると考えています。 これがUniswapV4のコアコンセプトであり、UniswapV4がどのように動作するかをより深く理解できることを願っています。 記事に誤りがある場合は、遠慮なくご指摘ください。 また、議論やフィードバックも歓迎します。

最後に、記事をレビューし、貴重なフィードバックを提供してくれた Anton Cheng 氏と Ping Chen 氏に感謝します。

免責事項:

  1. この記事は[medium]からの転載です。 すべての著作権は原著作者[林瑋宸Albert Lin]に帰属します。 この転載に異議がある場合は、Gate Learnチーム(gatelearn@gate.io)にご連絡いただければ、迅速に対応いたします。
  2. 免責事項:この記事で表明された見解や意見は、著者のものであり、投資アドバイスを構成するものではありません。
  3. 記事の他言語への翻訳は、Gate Learnチームによって行われます。 特に明記されていない限り、翻訳された記事を複製、配布、盗用することは禁止されています。
* La información no pretende ser ni constituye un consejo financiero ni ninguna otra recomendación de ningún tipo ofrecida o respaldada por Gate.io.
* Este artículo no se puede reproducir, transmitir ni copiar sin hacer referencia a Gate.io. La contravención es una infracción de la Ley de derechos de autor y puede estar sujeta a acciones legales.
Empieza ahora
¡Regístrate y recibe un bono de
$100
!
Crea tu cuenta