はじめに

初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

今回はBunzzの新機能『DeCipher』を使用して、UniswapV3の「TickLens」のコントラクトを見てみようと思います。

『DeCipher』はAIを使用してコントラクトのドキュメントを自動生成してくれるサービスです。

詳しい使い方に関しては以下の記事を参考にしてください!

この記事はあくまで個人が「DeCipher」を使用してみての記事になります。 間違った部分などもあるかと思いますが、その際はコメントなどで教えてください。

今回使用する『DeCipher』のリンクは以下になります。

Etherscanのリンクは以下になります。

概要

TickLens(ティックレンズ)コントラクトは、Uniswap V3プロトコルの一部であり、リクイディティプールに関する詳細な情報を提供します。

TickLensコントラクトは、複数のコントラクトと連携しています。 IUniswapV3Pool、IUniswapV3PoolImmutables、IUniswapV3PoolState、IUniswapV3PoolDerivedState、IUniswapV3PoolActions、IUniswapV3PoolOwnerActions、IUniswapV3PoolEventsなどが含まれます。 これらのコントラクトは、TickLensコントラクトがリクイディティプールの情報を提供するために必要な機能を提供します。

TickLensコントラクトの主な役割は、リクイディティプールに関連する情報を提供することです。 これは、前述のコントラクトの機能を活用して実現されます。 ユーザーは、プールの不変な特性、現在の状態、派生状態、アクション、オーナーアクション、イベントなどのデータに、TickLensコントラクトを通じてアクセスできます。

TickLensコントラクトを使用することで、ユーザーはプールのアドレス、トークンアドレス、手数料の段階、ティックの間隔などの特性に関する情報にアクセスできます。 さらに、ユーザーはプールの現在の状態を取得し、現在のティック、リクイディティ、手数料などの情報を確認できます。

さらに、TickLensコントラクトを利用して、特定のティックでリクイディティを提供する際に得られるトークン0とトークン1の量を計算したり、特定のティック範囲で追加または削除されるリクイディティの量を推定したりできます。

使い方

目標

コントラクトの目標は、テクニカルオペレータや開発者がUniswap V3プロトコルのリクイディティプールに関する情報を取得できることです。 これにより、プールの活動の監視やリクイディティポジションの分析、情報に基づいた取引の意思決定などが支援されます。

リクイディティプールの情報を取得する手順は以下の通りです。

1. TickLensコントラクトのデプロイ

EthereumネットワークにTickLensコントラクトのインスタンスをデプロイします。

2. コントラクトの初期化

TickLensコントラクトのコンストラクタを呼び出し、対象のUniswap V3プールのアドレスを指定します。

3. プール情報の取得

プールアドレスの取得 pool関数を呼び出し、TickLensコントラクトに関連付けられたUniswap V3プールのアドレスを取得します。

プールの情報の取得 getPoolImmutables関数を呼び出し、トークンアドレス、手数料、ティック間隔などのUniswap V3プールの不変な情報を取得します。

プールの状態の取得 getPoolState関数を呼び出し、現在のリクイディティ、ティックインデックス、リクイディティ範囲などのUniswap V3プールの状態を取得します。

プールの派生状態の取得 getPoolDerivedState関数を呼び出し、現在の価格、リクイディティの値、手数料などのUniswap V3プールの派生状態情報を取得します。

プールのアクションの取得 getPoolActions関数を呼び出し、ミンティング、バーニング、スワッピングなどのUniswap V3プールでサポートされるアクションを取得します。

プールのオーナーアクションの取得 getPoolOwnerActions関数を呼び出し、手数料の収集、ティックの更新などのUniswap V3プールでサポートされるオーナーアクションを取得します。

プールのイベントの取得 getPoolEvents関数を呼び出し、リクイディティの提供、スワップ、手数料の収集など、Uniswap V3プールから発生するイベントを取得します。

TickLensコントラクトの使用

1. リクイディティプールの活動のモニタリング

  • プールの現在のリクイディティとティックインデックスを取得することで、プールの活動をリアルタイムで監視できます。
  • プールの現在の価格とリクイディティ値を取得して、プールのパフォーマンスやトレンドをトラッキングできます。
  • プールから発生するイベントを取得して、過去の活動を分析し、取引の動向や出来事を把握できます。

2. リクイディティポジションの分析

  • プールのトークンアドレス、手数料、ティック間隔などの情報を取得して、プールの設定や特性を理解できます。
  • プールのリクイディティ範囲を知ることで、どの範囲でリクイディティを提供できるかを把握できます。
  • プールが収集した手数料を取得して、リクイディティポジションの収益性を評価し、最適なポジションを選択できます。

3. 情報に基づいた取引の意思決定

  • プールでサポートされるアクションを知ることで、取引オプションを理解し、効果的な戦略を構築できます。
  • プールでサポートされるオーナーアクションを把握して、プールの管理や操作に関する選択肢を理解できます。
  • プールの過去のイベントを分析して、取引のパターンや市場のトレンドを洞察し、効果的なトレード戦略を考案できます。

関連EIP/ERC

  • EIP1620: Money Streaming Standard(マネーストリーミングスタンダード)
    • TickLensコントラクトは、マネーストリーミングスタンダードを実装したリクイディティプールに関する情報を取得するために使用できます。
  • EIP20: トークンスタンダード
    • TickLensコントラクトは、Uniswap V3プールで使用されているERC-20トークンに関する情報を取得するために使用できます。

ERC20については以下を参考にしてください。

  • EIP721: ノンファンジブルトークンスタンダード
    • TickLensコントラクトは、Uniswap V3プールで使用されているERC-721トークンに関する情報を取得するために使用できます。

ERC721については以下を参考にしてください。

  • EIP1155: マルチトークンスタンダード
    • TickLensコントラクトは、Uniswap V3プールで使用されているERC-1155トークンに関する情報を取得するために使用できます。

ERC1155については以下を参考にしてください。

パラメーター

なし。

コントラクト

TickLens

PopulatedTick

PopulatedTick
solidity
struct PopulatedTick {
    int24 tick;
    int128 liquidityNet;
    uint128 liquidityGross;
}

概要 リクイディティプール内の特定のティックに関する情報を格納する構造体。

パラメータ

  • tick
    • int24型の変数で、ポピュレートされたティックの位置を示します。
    • ティックはリクイディティプール内での価格と位置を表します。
  • liquidityNet
    • int128型の変数で、ポピュレートされたティックの位置における純リクイディティを表します。
    • これは、特定のティックでの買い注文のリクイディティと売り注文のリクイディティの差を示します。
  • liquidityGross
    • uint128型の変数で、ポピュレートされたティックの位置における総リクイディティを表します。
    • これは、特定のティックの位置での全ての注文の総リクイディティを示します。

tick Uniswap V3プロトコルにおいて価格帯(価格レンジ)を表す重要な概念です。 特定の価格帯を表す整数値であり、リクイディティプール内での取引やリクイディティ供給の際に使用されます。

例えば、あるリクイディティプールが価格レンジを-15000から15000までカバーしているとします。 この場合、価格帯ごとにtickが設定されます。 価格帯ごとのtickは次のようになるとします。

  • 最低価格帯: -15000
  • 中央価格帯: 0
  • 最高価格帯: 15000

このように、価格レンジ内の各価格帯には対応する整数のtickが割り当てられます。 価格が上昇するにつれてtickの値も増加し、価格が下降するにつれてtickの値も減少します。 価格帯が小さくなると、隣接するtickの値も近づいてきます。

リクイディティプール内での取引やリクイディティ供給では、特定の価格帯におけるtickを指定して操作を行います。 これにより、価格帯ごとのリクイディティ供給やトレードが可能になります。 価格が変動するたびに、価格帯ごとに対応するtickも変動し、プール内のリクイディティが調整されます。

まとめると、tickはリクイディティプール内で価格帯を示す整数値であり、価格の変動に応じて変動する重要な指標です。

liquidityNet 特定のティックにおけるリクイディティの純量(Net Liquidity)を表します。

例えば、あるリクイディティプールにおいて特定のティックに対する liquidityNet が次のようになるとしましょう。

  • ティック: 5000
  • liquidityNet: 1500

この場合、ティック5000におけるliquidityNetは1500です。 これは、ティック5000においてプールに提供されたリクイディティの合計が1500であることを示しています。 具体的には、プールにリクイディティを提供する際、特定の価格帯(ティック)にリクイディティを供給した結果、その価格帯内のリクイディティの合計が1500であることを示しています。

liquidityNetは正の値や負の値を持つことがあります。 正の値の場合、特定のティックにおいてプールに提供されたリクイディティが純増していることを示し、負の値の場合は減少していることを示します。 例えば、ティック5000においてliquidityNetが正の値であれば、そのティックでのリクイディティ供給が増加していることを示します。

まとめると、liquidityNetは特定のティックでのリクイディティの増減を表す値であり、そのティックにおけるプールのリクイディティの変動を示す重要な指標です。

liquidityGross Uniswap V3プール内での特定の価格帯における総リクイディティの量を表す値です。

例えば、あるリクイディティプール内の特定の価格帯において、合計で1000ドル分のリクイディティが提供されているとします。 この場合、その価格帯におけるliquidityGrossは1000となります。

ここで、注意すべきポイントは、liquidityGrossがその価格帯内の全てのリクイディティの総量を表すということです。 つまり、その価格帯でプールに供給されている全てのトークン量を合算した値を示します。

liquidityGrossは、価格帯ごとに異なる価格でのリクイディティ供給を表すため、特定の価格帯におけるリクイディティの状況を理解するための重要な指標です。 価格帯ごとのliquidityGrossの変動は、価格帯のリクイディティ供給がどのように変化しているかを把握するのに役立ちます。

まとめると、liquidityGrossは特定の価格帯におけるリクイディティの総量を表す値であり、その価格帯のリクイディティ供給の状況を示す重要な情報です。

getPopulatedTicksInWord

getPopulatedTicksInWord
solidity
function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex)
    public
    view
    override
    returns (PopulatedTick[] memory populatedTicks)
{
    // fetch bitmap
    uint256 bitmap = IUniswapV3Pool(pool).tickBitmap(tickBitmapIndex);

    // calculate the number of populated ticks
    uint256 numberOfPopulatedTicks;
    for (uint256 i = 0; i < 256; i++) {
        if (bitmap & (1 << i) > 0) numberOfPopulatedTicks++;
    }

    // fetch populated tick data
    int24 tickSpacing = IUniswapV3Pool(pool).tickSpacing();
    populatedTicks = new PopulatedTick[](numberOfPopulatedTicks);
    for (uint256 i = 0; i < 256; i++) {
        if (bitmap & (1 << i) > 0) {
            int24 populatedTick = ((int24(tickBitmapIndex) << 8) + int24(i)) * tickSpacing;
            (uint128 liquidityGross, int128 liquidityNet, , , , , , ) = IUniswapV3Pool(pool).ticks(populatedTick);
            populatedTicks[--numberOfPopulatedTicks] = PopulatedTick({
                tick: populatedTick,
                liquidityNet: liquidityNet,
                liquidityGross: liquidityGross
            });
        }
    }
}

概要 指定されたリクイディティプールの特定のビットマップに基づいて、PopulatedTick構造体の配列を返します。

詳細

  • 引数としてリクイディティプールのアドレスとビットマップのインデックスを指定します。
  • 関数はビットマップからポピュレートされたティックの数を計算し、それに基づいてPopulatedTick構造体の配列を作成します。
  • PopulatedTick構造体の配列には、ポピュレートされたティックのデータが含まれます。
    • ティックの位置、リクイディティのネット値、およびグロス値が記録されます。

ポピュレートされた 一般的には「データや情報が入力または埋め込まれた」という意味です。 リクイディティプール内の特定のティックの位置に関連する情報が存在することを指しています。 具体的には、TickLensコントラクトの関数 getPopulatedTicksInWord は、プール内の特定のビットマップによって示されるティックの位置におけるリクイディティやその他のデータが存在することを示しています。

ビットマップは、複数のビット(0または1の値を持つ単位)で構成されるデータ構造であり、通常は集合やフラグの表現に使用されます。 ビットマップは効率的なデータ格納方法であり、特定の要素が存在するかどうかを高速に判定するのに適しています。 ここでは、ビットマップの基本的な概念と具体例について説明します。

例えば、3つの要素を持つセットがあるとします。 これらの要素は{A, B, C}の3つです。 このセットをビットマップで表現する場合、以下のようになります。

  • A: 1 (存在することを示す)
  • B: 0 (存在しないことを示す)
  • C: 1 (存在することを示す)

上記のビットマップは、101というビット列で表現されます。 各ビットは、要素が存在するかどうかを示しています。 この例では、AとCがセット内に存在することを示すため、対応するビットが1になっています。 Bはセット内に存在しないため、対応するビットが0になっています。

TickLensコントラクトの例で言えば、getPopulatedTicksInWord関数が使用するビットマップは、リクイディティプール内の特定のティックのポピュレート状態を示すために使用されます。 各ビットはティックの存在または不在を表し、1はティックがポピュレートされていることを示し、0はティックがポピュレートされていないことを示します。 ビットマップを使用することで、リクイディティプール内の多数のティックの状態を効率的に表現することができます。

引数

  • pool
    • 情報を取得したいUniswap V3プールのアドレス。
  • tickBitmapIndex
    • ビットマップのインデックス。

戻り値

  • PopulatedTick[] memory populatedTicks
    • PopulatedTick構造体の配列。
    • 各要素は、ティックの位置、リクイディティのネット値、リクイディティのグロス値を含みます。

コード

ITickLens

ITickLens.sol
solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;

/// @title Tick Lens
/// @notice Provides functions for fetching chunks of tick data for a pool
/// @dev This avoids the waterfall of fetching the tick bitmap, parsing the bitmap to know which ticks to fetch, and
/// then sending additional multicalls to fetch the tick data
interface ITickLens {
    struct PopulatedTick {
        int24 tick;
        int128 liquidityNet;
        uint128 liquidityGross;
    }

    /// @notice Get all the tick data for the populated ticks from a word of the tick bitmap of a pool
    /// @param pool The address of the pool for which to fetch populated tick data
    /// @param tickBitmapIndex The index of the word in the tick bitmap for which to parse the bitmap and
    /// fetch all the populated ticks
    /// @return populatedTicks An array of tick data for the given word in the tick bitmap
    function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex)
        external
        view
        returns (PopulatedTick[] memory populatedTicks);
}

TickLens

TickLens.sol
solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';

import '../interfaces/ITickLens.sol';

/// @title Tick Lens contract
contract TickLens is ITickLens {
    /// @inheritdoc ITickLens
    function getPopulatedTicksInWord(address pool, int16 tickBitmapIndex)
        public
        view
        override
        returns (PopulatedTick[] memory populatedTicks)
    {
        // fetch bitmap
        uint256 bitmap = IUniswapV3Pool(pool).tickBitmap(tickBitmapIndex);

        // calculate the number of populated ticks
        uint256 numberOfPopulatedTicks;
        for (uint256 i = 0; i < 256; i++) {
            if (bitmap & (1 << i) > 0) numberOfPopulatedTicks++;
        }

        // fetch populated tick data
        int24 tickSpacing = IUniswapV3Pool(pool).tickSpacing();
        populatedTicks = new PopulatedTick[](numberOfPopulatedTicks);
        for (uint256 i = 0; i < 256; i++) {
            if (bitmap & (1 << i) > 0) {
                int24 populatedTick = ((int24(tickBitmapIndex) << 8) + int24(i)) * tickSpacing;
                (uint128 liquidityGross, int128 liquidityNet, , , , , , ) = IUniswapV3Pool(pool).ticks(populatedTick);
                populatedTicks[--numberOfPopulatedTicks] = PopulatedTick({
                    tick: populatedTick,
                    liquidityNet: liquidityNet,
                    liquidityGross: liquidityGross
                });
            }
        }
    }
}

最後に

今回の記事では、Bunzzの新機能『DeCipher』を使用して、UniswapV3の「TickLens」のコントラクトを見てきました。 いかがだったでしょうか? 今後も特定のNFTやコントラクトをピックアップしてまとめて行きたいと思います。

普段はブログやQiitaでブロックチェーンやAIに関する記事を挙げているので、よければ見ていってください!