🦄

[Bunzz Decipher] UniswapV3の『NonfungiblePositionManager』コントラクトを理解しよう!

2023/09/01に公開

はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

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

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

https://www.bunzz.dev/decipher

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

https://zenn.dev/heku/articles/33266f0c19d523

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

https://app.bunzz.dev/decipher/chains/1/addresses/0xc36442b4a4522e871399cd717abdd847ab11fe88

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

https://etherscan.io/address/0xC36442b4a4522E871399CD717aBDD847Ab11FE88

概要

Uniswap v3プロトコルの中心的な要素であるNonfungiblePositionManager(非代替ポジションマネージャー)コントラクトは、Uniswap v3の流動性プール内に存在する特別なポジションを管理するのが役割です。
このコントラクトは、さまざまな他のコントラクトと合わせて使用されます。

ユーザーがこのコントラクトを通じて行えることは、主に2つあります。
1つ目は、特定の価格範囲(ティックと呼ばれます)内に流動性を提供するためのポジションを作成すること。
2つ目は、ポジションの所有権の取り扱いや転送を管理し、ポジションに関する情報(たとえば、トークンの情報)を提供します。

さらに、ポジションに関連する様々なアクション(ミントやバーンなど)を検証し、実行します。
他のコントラクトと連携して、ポジションの作成や管理に関する支援も行います。
セキュリティや正確性を確保するために、検証メカニズムも備えています。

使い方

NonfungiblePositionManagerコントラクトは、Uniswap V3流動性プール内の非代替ポジションを管理するスマートコントラクトです。
ユーザーはこのコントラクトを使用して、プール内でポジションの作成、更新、削除などの操作を行うことができます。
また、流動性管理に関連する他の操作も行えます。

コントラクトの目標

NonfungiblePositionManagerコントラクトの目標は、Uniswap V3流動性プール内の非代替ポジションを管理するための標準化されたインターフェースを提供することです。
開発者は、このコントラクトと対話してポジションの作成、更新、削除などの操作を行うことができるようになります。
また、流動性管理に関連する他の操作も実行できます。

使用方法

Uniswap V3流動性プール内の非代替ポジションを管理するためには、次の手順を実行できます。

  1. NonfungiblePositionManagerコントラクトをデプロイします。
  2. createAndInitializePoolIfNecessary関数を呼び出して、新しい流動性プールを作成します(存在しない場合)。
  3. createPosition関数を呼び出して、指定された流動性プール内に新しい非代替ポジションを作成します。
  4. increasePosition関数を呼び出して、既存の非代替ポジションの流動性を増やします。
  5. decreasePosition関数を呼び出して、既存の非代替ポジションの流動性を減らします。
  6. collectPosition関数を呼び出して、非代替ポジションに溜まった手数料を収集します。
  7. burnPosition関数を呼び出して、非代替ポジションを削除し、溜まった手数料を収集します。
  8. mint関数を呼び出して、非代替ポジションを表す新しいERC721トークンを作成します。
  9. burn関数を呼び出して、非代替ポジションを表す既存のERC721トークンを破棄します。

関数

createAndInitializePoolIfNecessary

存在しないなら、新しい流動性プールを作成します。

createPosition

流動性プール内に新しい非代替ポジションを作成します。

increasePosition

既存の非代替ポジションの流動性を増やします。

decreasePosition

既存の非代替ポジションの流動性を減らします。

collectPosition

非代替ポジションが溜めた手数料を収集します。

burnPosition

非代替ポジションを削除し、溜めた手数料を収集します。

mint

非代替ポジションを表す新しいERC721トークンを作成します。

burn

非代替ポジションを表す既存のERC721トークンを破棄します。

イベント

PositionCreated

新しい非代替ポジションが作成された時に発行されます。

PositionIncreased

既存の非代替ポジションの流動性が増加した時に発行されます。

PositionDecreased

既存の非代替ポジションの流動性が減少した時に発行されます。

PositionCollected

非代替ポジションが蓄積した手数料が収集された時に発行されます。

PositionBurned

非代替ポジションが削除され、溜めた手数料が収集された時に発行されます。

TokenMinted

新しいERC721トークンが作成され時に発行されます。

TokenBurned

既存のERC721トークンが破棄され、非代替ポジションを表すトークンが消去された時に発行されます。

パラメータ

_factory

_factoryはUniswap V3ファクトリーコントラクトのアドレスです。
このファクトリーコントラクトは、Uniswap V3流動性プールを作成するための基盤となる役割を果たします。

_WETH9

WETH9コントラクトのアドレスです。
WETH9Wrapped Ether の略で、Ethereumのイーサー(ETH)をEthereumのERC20トークンであるWETHとして表現したものです。
これにより、イーサーを他のトークンと同じように取り扱えるようになります。

_tokenDescriptor

トークンディスクリプターコントラクトのアドレスです。
このコントラクトは、トークン(通貨やアセット)の詳細な情報やメタデータ(例: トークン名、シンボル)するため、トークンの理解や識別が容易になります。

コントラクト

NonfungiblePositionManager

もちろんです。以下にそれぞれの変数やマッピングについて、日本語でわかりやすく説明いたします。

Position

Position
struct Position {
        uint96 nonce;
        address operator;
        uint80 poolId;
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
        uint256 feeGrowthInside0LastX128;
        uint256 feeGrowthInside1LastX128;
        uint128 tokensOwed0;
        uint128 tokensOwed1;
}

概要
Uniswapのポジションに関する詳細情報を保持するデータ構造。

詳細

  • nonce
    • パーミットのためのノンス(一意の識別子)。
  • operator
    • トークンのを送る許可を与えられたアドレス。
  • poolId
    • このトークンが関連づけられているプールのID。
  • tickLowertickUpper
    • ポジションの価格範囲を示すティックの範囲。
  • liquidity
    • ポジションの流動性(供給量)。
  • feeGrowthInside0LastX128feeGrowthInside1LastX128
    • 個別のポジションの最後のアクション時点での集計ポジションの手数料成長率。
  • tokensOwed0tokensOwed1
    • 最後の計算時点でポジションに支払われるトークン。

_poolIds

mapping(address => uint80) private _poolIds;

概要
アドレスに対応するプールIDを格納するプライベートなマッピング。

_poolIdToPoolKey

mapping(uint80 => PoolAddress.PoolKey) private _poolIdToPoolKey;

概要
プールIDに対応するプールキーを保存するマッピング配列。

_positions

mapping(uint256 => Position) private _positions;

概要
トークンIDに対応するポジションデータを保存するマッピング配列。

_nextId

uint176 private _nextId = 1;

概要
次に発行されるトークンのIDを示す変数。

_nextPoolId

uint80 private _nextPoolId = 1;

概要
次に使用されるプールのIDを示す変数。

_tokenDescriptor

address private immutable _tokenDescriptor;

概要
ポジショントークンのURIを生成するためのトークンディスクリプターコントラクトのアドレス。

もちろんです。それでは、提供されたコード内の全ての関数について解説いたします。


positions

positions
function positions(uint256 tokenId)
        external
        view
        override
        returns (
            uint96 nonce,
            address operator,
            address token0,
            address token1,
            uint24 fee,
            int24 tickLower,
            int24 tickUpper,
            uint128 liquidity,
            uint256 feeGrowthInside0LastX128,
            uint256 feeGrowthInside1LastX128,
            uint128 tokensOwed0,
            uint128 tokensOwed1
        )
    {
        Position memory position = _positions[tokenId];
        require(position.poolId != 0, 'Invalid token ID');
        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];
        return (
            position.nonce,
            position.operator,
            poolKey.token0,
            poolKey.token1,
            poolKey.fee,
            position.tickLower,
            position.tickUpper,
            position.liquidity,
            position.feeGrowthInside0LastX128,
            position.feeGrowthInside1LastX128,
            position.tokensOwed0,
            position.tokensOwed1
        );
}

概要
指定されたトークンIDに関連するポジションの詳細情報を取得する関数。

引数

  • tokenId
    • 取得するポジションのトークンID。

戻り値

  • nonce
    • ポジションのnonce。
  • operator
    • オペレーターのアドレス。
  • token0
    • トークン0のアドレス。
  • token1
    • トークン1のアドレス。
  • fee
    • 取引手数料。
  • tickLower
    • 価格帯の下限。
  • tickUpper
    • 価格帯の上限。
  • liquidity
    • 流動性。
  • feeGrowthInside0LastX128
    • トークン0の取引手数料成長率。
  • feeGrowthInside1LastX128
    • トークン1の取引手数料成長率。
  • tokensOwed0
    • 未払いのトークン0量。
  • tokensOwed1
    • 未払いのトークン1量。

cachePoolKey

cachePoolKey
function cachePoolKey(address pool, PoolAddress.PoolKey memory poolKey) private returns (uint80 poolId) {
        poolId = _poolIds[pool];
        if (poolId == 0) {
            _poolIds[pool] = (poolId = _nextPoolId++);
            _poolIdToPoolKey[poolId] = poolKey;
        }
}

概要
プールのアドレスとプールキーをキャッシュする関数。

詳細
指定されたプールのアドレスとプールキーをキャッシュし、プールIDを返します。
キャッシュされたプールキーは後で参照するために保存されます。

引数

  • pool
    • プールのアドレス。
  • poolKey
    • キャッシュするプールキーの情報。

戻り値

  • poolId
    • キャッシュされたプールID。

mint

mint
function mint(MintParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        IUniswapV3Pool pool;
        (liquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: params.token0,
                token1: params.token1,
                fee: params.fee,
                recipient: address(this),
                tickLower: params.tickLower,
                tickUpper: params.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min
            })
        );

        _mint(params.recipient, (tokenId = _nextId++));

        bytes32 positionKey = PositionKey.compute(address(this), params.tickLower, params.tickUpper);
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);

        // idempotent set
        uint80 poolId =
            cachePoolKey(
                address(pool),
                PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee})
            );

        _positions[tokenId] = Position({
            nonce: 0,
            operator: address(0),
            poolId: poolId,
            tickLower: params.tickLower,
            tickUpper: params.tickUpper,
            liquidity: liquidity,
            feeGrowthInside0LastX128: feeGrowthInside0LastX128,
            feeGrowthInside1LastX128: feeGrowthInside1LastX128,
            tokensOwed0: 0,
            tokensOwed1: 0
        });

        emit IncreaseLiquidity(tokenId, liquidity, amount0, amount1);
}

概要
流動性を追加してトークンを発行する関数。

詳細
指定されたパラメータに基づいて流動性を追加し、トークンを発行します。
流動性を追加する際に、指定された条件に従ってトークン0およびトークン1を追加するプロセスが行われます。
また、追加されたトークンに対する取引手数料成長率も更新されます。

引数

  • params
    • 流動性追加のためのパラメータ。

戻り値

  • tokenId
    • 発行されたトークンID。
  • liquidity
    • 追加された流動性。
  • amount0
    • 追加されたトークン0の量。
  • amount1
    • 追加されたトークン1の量。

increaseLiquidity

increaseLiquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        Position storage position = _positions[params.tokenId];

        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];

        IUniswapV3Pool pool;
        (liquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: poolKey.token0,
                token1: poolKey.token1,
                fee: poolKey.fee,
                tickLower: position.tickLower,
                tickUpper: position.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min,
                recipient: address(this)
            })
        );

        bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper);

        // this is now updated to the current transaction
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);

        position.tokensOwed0 += uint128(
            FullMath.mulDiv(
                feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
                position.liquidity,
                FixedPoint128.Q128
            )
        );
        position.tokensOwed1 += uint128(
            FullMath.mulDiv(
                feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
                position.liquidity,
                FixedPoint128.Q128
            )
        );

        position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
        position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
        position.liquidity += liquidity;

        emit IncreaseLiquidity(params.tokenId, liquidity, amount0, amount1);
}

概要
既存のポジションに流動性を追加する関数。

詳細
指定されたパラメータに基づいて既存のポジションに流動性を追加します。
追加される流動性は、指定された条件に従ってトークン0およびトークン1を追加するプロセスが行われることで計算されます。
また、追加されたトークンに対する取引手数料成長率も更新されます。

引数

  • params
    • 流動性追加のためのパラメータ。

戻り値

  • liquidity
    • 追加された流動性。
  • amount0
    • 追加されたトークン0の量。
  • amount1
    • 追加されたトークン1の量。

decreaseLiquidity

decreaseLiquidity
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
        external
        payable
        override
        isAuthorizedForToken(params.tokenId)
        checkDeadline(params.deadline)
        returns (uint256 amount0, uint256 amount1)
    {
        require(params.liquidity > 0);
        Position storage position = _positions[params.tokenId];

        uint128 positionLiquidity = position.liquidity;
        require(positionLiquidity >= params.liquidity);

        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];
        IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
        (amount0, amount1) = pool.burn(position.tickLower, position.tickUpper, params.liquidity);

        require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');

        bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper);
        // this is now updated to the current transaction
        (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(positionKey);

        position.tokensOwed0 +=
            uint128(amount0) +
            uint128(
                FullMath.mulDiv(
                    feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
                    positionLiquidity,
                    FixedPoint128.Q128
                )
            );
        position.tokensOwed1 +=
            uint128(amount1) +
            uint128(
                FullMath.mulDiv(
                    feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
                    positionLiquidity,
                    FixedPoint128.Q128
                )
            );

        position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
        position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
        // subtraction is safe because we checked positionLiquidity is gte params.liquidity
        position.liquidity = positionLiquidity - params.liquidity;

        emit DecreaseLiquidity(params.tokenId, params.liquidity, amount0, amount1);
}

概要
既存のポジションから流動性を減少させる関数。

詳細
指定されたパラメータに基づいて既存のポジションから流動性を減少させます。
減少する流動性は、指定された条件に従って計算され、トークン0およびトークン1の量が返されます。また、減少された流動性に応じて取引手数料成長率も更新されます。

引数

  • params
    • 流動性減少のためのパラメータ。

戻り値

  • amount0
    • 減少したトークン0の量。
  • amount1
    • 減少したトークン1の量。

tokenURI

tokenURI
function tokenURI(uint256 tokenId) public view override(ERC721, IERC721Metadata) returns (string memory) {
        require(_exists(tokenId));
        return INonfungibleTokenPositionDescriptor(_tokenDescriptor).tokenURI(this, tokenId);
}

概要
指定されたトークンIDに関連するトークンのメタデータURIを取得する関数。

詳細
指定されたトークンIDに関連するトークンのメタデータURIを取得します。
このURIは、トークンの外部メタデータの場所を示すために使用されます。

引数

  • tokenId
    • 取得するトークンのトークンID。

戻り値

  • string
    • トークンのメタデータURI。

baseURI

baseURI
function baseURI() public pure override returns (string memory) {}

概要
ベースURIを取得する関数。

詳細
ベースURIを取得します。
この関数の実装は省略されているため、カスタムして使用します。

戻り値

  • string
    • ベースURI。

isAuthorizedForToken

isAuthorizedForToken
modifier isAuthorizedForToken(uint256 tokenId) {
        require(_isApprovedOrOwner(msg.sender, tokenId), 'Not approved');
        _;
}

概要
与えられた tokenId に関連するトークンの所有者または承認されたアドレスであるかどうかを確認する修飾子。

詳細
関数実行前に _isApprovedOrOwner関数を呼び出して、実行者(msg.sender)が与えられた tokenIdの所有者または承認されたアドレスであるかどうかを確認します。
もし条件が満たされていない場合、requireステートメントは例外をスローし、エラーメッセージ **"Not approved"**を表示します。

パラメータ

  • tokenId
    • 関数がチェックする対象のトークンのID。

collect 関数

collect
function collect(CollectParams calldata params)
        external
        payable
        override
        isAuthorizedForToken(params.tokenId)
        returns (uint256 amount0, uint256 amount1)
    {
        require(params.amount0Max > 0 || params.amount1Max > 0);
        // allow collecting to the nft position manager address with address 0
        address recipient = params.recipient == address(0) ? address(this) : params.recipient;

        Position storage position = _positions[params.tokenId];

        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];

        IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

        (uint128 tokensOwed0, uint128 tokensOwed1) = (position.tokensOwed0, position.tokensOwed1);

        // trigger an update of the position fees owed and fee growth snapshots if it has any liquidity
        if (position.liquidity > 0) {
            pool.burn(position.tickLower, position.tickUpper, 0);
            (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) =
                pool.positions(PositionKey.compute(address(this), position.tickLower, position.tickUpper));

            tokensOwed0 += uint128(
                FullMath.mulDiv(
                    feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
                    position.liquidity,
                    FixedPoint128.Q128
                )
            );
            tokensOwed1 += uint128(
                FullMath.mulDiv(
                    feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
                    position.liquidity,
                    FixedPoint128.Q128
                )
            );

            position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
            position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
        }

        // compute the arguments to give to the pool#collect method
        (uint128 amount0Collect, uint128 amount1Collect) =
            (
                params.amount0Max > tokensOwed0 ? tokensOwed0 : params.amount0Max,
                params.amount1Max > tokensOwed1 ? tokensOwed1 : params.amount1Max
            );

        // the actual amounts collected are returned
        (amount0, amount1) = pool.collect(
            recipient,
            position.tickLower,
            position.tickUpper,
            amount0Collect,
            amount1Collect
        );

        // sometimes there will be a few less wei than expected due to rounding down in core, but we just subtract the full amount expected
        // instead of the actual amount so we can burn the token
        (position.tokensOwed0, position.tokensOwed1) = (tokensOwed0 - amount0Collect, tokensOwed1 - amount1Collect);

        emit Collect(params.tokenId, recipient, amount0Collect, amount1Collect);
}

概要
ポジションから未払いのトークンを収集する関数。

詳細
指定されたパラメータに基づいてポジションから未払いのトークンを収集します。
収集されるトークンの量は、指定された最大量とポジション内の未払いトークン量の間で調整されます。
収集が行われる際には、収集先のアドレスや収集するトークンの種類も指定できます。
収集されたトークンの量が戻り値として返されます。

引数

  • params
    • 収集のためのパラメータ。

戻り値

  • amount0
    • 収集されたトークン0の量。
  • amount1
    • 収集されたトークン1の量。

burn

burn
function burn(uint256 tokenId) external payable override isAuthorizedForToken(tokenId) {
        Position storage position = _positions[tokenId];
        require(position.liquidity == 0 && position.tokensOwed0 == 0 && position.tokensOwed1 == 0, 'Not cleared');
        delete _positions[tokenId];
        _burn(tokenId);
}

概要
指定されたトークンIDに対するポジションを削除する関数。

詳細
指定されたトークンIDに対するポジションを削除します。
ポジションが削除される際には、そのポジションに関連する情報がクリアされます。

引数

  • tokenId
    • 削除するポジションのトークンID。

_getAndIncrementNonce

_getAndIncrementNonce
function _getAndIncrementNonce(uint256 tokenId) internal override returns (uint256) {
        return uint256(_positions[tokenId].nonce++);
}

概要
指定されたトークンIDに対するnonceを取得して増加する関数。

詳細
指定されたトークンIDに関連するポジションのnonceを取得してから、そのnonceを増加させます。

引数

  • tokenId
    • nonceを取得および増加させるポジションのトークンID。

戻り値

  • uint256
    • 取得されたnonce

getApproved

getApproved
function getApproved(uint256 tokenId) public view override(ERC721, IERC721) returns (address) {
        require(_exists(tokenId), 'ERC721: approved query for nonexistent token');

        return _positions[tokenId].operator;
}

概要
指定されたトークンIDに対する承認済みのアドレスを取得する関数。

詳細
指定されたトークンIDに関連するポジションのオペレーターアドレスを取得します。
これにより、トークンの承認済み状態を確認できます。

引数

  • tokenId
    • 承認情報を取得するトークンID。

戻り値

  • address
    • 承認済みのアドレス。

_approve

_approve
function _approve(address to, uint256 tokenId) internal override(ERC721) {
        _positions[tokenId].operator = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
}

概要
指定されたアドレスに対してトークンを承認する関数。

詳細
この関数は、指定されたアドレスに対してトークンを承認し、ポジションのオペレーターアドレスを更新します。
これにより、トークンの所有者が別のアドレスに対してトークンの操作を許可できます。

引数

  • to
    • 承認するアドレス。
  • tokenId
    • 承認するトークンのトークンID。

戻り値

  • なし

LiquidityManagement

MintCallbackData

MintCallbackData
struct MintCallbackData {
    PoolAddress.PoolKey poolKey;
    address payer;
}

概要
Uniswap V3のミントコールバック時のデータを格納する構造体。

パラメーター

  • poolKey
    • トークンと手数料の情報を格納するPoolAddress.PoolKey型。
  • payer
    • ミントトランザクションを起こしたアドレス。

uniswapV3MintCallback

uniswapV3MintCallback
function uniswapV3MintCallback(
    uint256 amount0Owed,
    uint256 amount1Owed,
    bytes calldata data
) external override {
    MintCallbackData memory decoded = abi.decode(data, (MintCallbackData));
    CallbackValidation.verifyCallback(factory, decoded.poolKey);

    if (amount0Owed > 0) pay(decoded.poolKey.token0, decoded.payer, msg.sender, amount0Owed);
    if (amount1Owed > 0) pay(decoded.poolKey.token1, decoded.payer, msg.sender, amount1Owed);
}

概要
Uniswap V3のミントコールバックとして機能し、新しい流動性プールのトークンをミントした時に呼ばれる関数。

詳細
Uniswap V3の流動性マイントイベントに対するコールバックとして定義されています。
ミントされたトークンの残高に基づいて、トークンの所有者に対して支払いを行います。

引数

  • amount0Owed
    • トークン0の支払い額。
  • amount1Owed
    • トークン1の支払い額。
  • data
    • コールバックのデータ。

戻り値
なし。


AddLiquidityParams

AddLiquidityParams
struct AddLiquidityParams {
    address token0;
    address token1;
    uint24 fee;
    address recipient;
    int24 tickLower;
    int24 tickUpper;
    uint256 amount0Desired;
    uint256 amount1Desired;
    uint256 amount0Min;
    uint256 amount1Min;
}

概要
Uniswap V3の流動性を追加する際に使用される構造体。

詳細
Uniswap V3の流動性を追加する際に必要なパラメータをまとめています。
それぞれのパラメータは、新しい流動性プールの作成に関する情報を提供します。

パラメータ

  • token0
    • トークン0のアドレス。
  • token1
    • トークン1のアドレス。
  • fee
    • 取引手数料のレベルを示す数値。
  • recipient
    • 流動性プールのトークン所有者のアドレス。
  • tickLower
    • 価格レンジの下限を示す数値。
  • tickUpper
    • 価格レンジの上限を示す数値。
  • amount0Desired
    • 望ましいトークン0の供給量。
  • amount1Desired
    • 望ましいトークン1の供給量。
  • amount0Min
    • 最小限に供給するトークン0の量。
  • amount1Min
    • 最小限に供給するトークン1の量。

addLiquidity

addLiquidity
function addLiquidity(AddLiquidityParams memory params)
    internal
    returns (
        uint128 liquidity,
        uint256 amount0,
        uint256 amount1,
        IUniswapV3Pool pool
    )
{
    PoolAddress.PoolKey memory poolKey =
        PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee});

    pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));

    // compute the liquidity amount
    {
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
        uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
        uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(params.tickUpper);

        liquidity = LiquidityAmounts.getLiquidityForAmounts(
            sqrtPriceX96,
            sqrtRatioAX96,
            sqrtRatioBX96,
            params.amount0Desired,
            params.amount1Desired
        );
    }

    (amount0, amount1) = pool.mint(
        params.recipient,
        params.tickLower,
        params.tickUpper,
        liquidity,
        abi.encode(MintCallbackData({poolKey: poolKey, payer: msg.sender}))
    );

    require(amount0 >= params.amount0Min && amount1 >= params.amount1Min, 'Price slippage check');
}

概要
初期化されたプールに流動性を追加する関数。

詳細
Uniswap V3のプールに流動性を追加するための手順を実行します。
指定されたパラメータから、新しい流動性の量や価格レンジを計算し、プールに流動性を供給します。
価格スリッページのチェックも行います。

引数

  • params
    • 流動性を追加する際のパラメータを格納した構造体。

戻り値

  • liquidity
    • 追加された流動性の量。
  • amount0
    • トークン0の実際に供給された量。
  • amount1
    • トークン1の実際に供給された量。
  • pool
    • 追加された流動性の対象となるUniswap V3プールのインスタンス。

Multicall

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

https://zenn.dev/cryptogames/articles/197e0f1ed053ac#multicall

ERC721Permit

_getAndIncrementNonce

_getAndIncrementNonce
function _getAndIncrementNonce(uint256 tokenId) internal virtual returns (uint256);

概要
トークンIDに対する現在のnonce値を取得し、元の値を返す関数。

詳細
ERC721トークンの許可関連の操作で使用されるnonce値の管理を行います。
トークンIDを引数として受け取り、それに関連するnonce値を取得します。
そして、取得したnonce値を1増やします。
これにより、セキュリティと正確性が保たれます。

引数

  • tokenId
    • トークンの識別子であり、関連するnonce値を取得・更新する。

戻り値

  • uint256
    • 取得したnonce値を返します。
    • 元のnonce値は1増加しています。

nameHash, versionHash

コンストラクタ
bytes32 private immutable nameHash;
bytes32 private immutable versionHash;

概要
許可関連の署名検証に使用される名前とバージョン情報のハッシュ値を格納する変数。

詳細
nameHashは、トークン許可関連の署名検証時に使用される名前のハッシュ値を保持します。
同様に、versionHashはバージョン情報のハッシュ値を保持します。
これらのハッシュ値は、セキュリティを強化するために署名検証プロセスで使用されます。


constructor

constructor
constructor(
    string memory name_,
    string memory symbol_,
    string memory version_
) ERC721(name_, symbol_) {
    nameHash = keccak256(bytes(name_));
    versionHash = keccak256(bytes(version_));
}

概要
トークンの名前、シンボル、およびバージョン情報を受け取り、それぞれのハッシュ値を計算して設定します。

詳細
このコンストラクタは、ERC721トークンの名前とシンボル、さらにはバージョン情報を受け取ります。
それぞれの文字列からハッシュ値を計算し、nameHashversionHash変数に格納します。
これにより、後続の署名検証プロセスで使用されるハッシュ値が事前に準備されます。

引数

  • name_
    • トークンの名前。
  • symbol_
    • トークンのシンボル。
  • version_
    • トークンのバージョン情報。

DOMAIN_SEPARATOR

DOMAIN_SEPARATOR
function DOMAIN_SEPARATOR() public view override returns (bytes32) {
    return
        keccak256(
            abi.encode(
                0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
                nameHash,
                versionHash,
                ChainId.get(),
                address(this)
            )
        );
}

概要
許可関連の署名検証時に使用されるEIP712ドメインセパレータの値を計算して返す関数。

詳細
EIP712の仕様に従って、ドメインセパレータの値を計算します。
nameHashversionHash、ネットワークのチェーンID、およびコントラクトのアドレスを組み合わせてハッシュ値を生成します。
これにより、署名検証プロセスの一環として使用されるドメインセパレータが生成されます。


PERMIT_TYPEHASH

bytes32 public constant override PERMIT_TYPEHASH =
        0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad;

概要
許可関連の署名検証に使用される特定の型ハッシュ値を表す定数。

詳細
特定の型のデータを表すハッシュ値です。
このハッシュ値は、許可関連の署名データの検証に使用されます。
署名検証の際に、特定のデータ構造を正当性を確認するために使用します。


permit

permit
function permit(
    address spender,
    uint256 tokenId,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external payable override {
    require(_blockTimestamp() <= deadline, 'Permit expired');

    bytes32 digest =
        keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR(),
                keccak256(abi.encode(PERMIT_TYPEHASH, spender, tokenId, _getAndIncrementNonce(tokenId), deadline))
            )
        );
    address owner = ownerOf(tokenId);
    require(spender != owner, 'ERC721Permit: approval to current owner');

    if (Address.isContract(owner)) {
        require(IERC1271(owner).isValidSignature(digest, abi.encodePacked(r, s, v)) == 0x1626ba7e, 'Unauthorized');
    } else {
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0), 'Invalid signature');
        require(recoveredAddress == owner, 'Unauthorized');
    }

    _approve(spender, tokenId);
}

概要
ERC721トークンの許可を付与する関数。

詳細
ERC721トークンの所有者によって実行されることを前提としています。
関数のパラメータとして、許可を付与する対象のアドレス (spender)、対象のトークンID (tokenId)、許可の有効期限 (deadline)、署名のv値 (v)、r値 (r)、およびs値 (s) が与えられます。

関数内では、まず有効期限が現在のブロックタイムスタンプより前であることを確認し、許可が有効であることを確認します。
その後、特定のメッセージからハッシュ値を計算し、署名検証に使用します。
トークンの所有者がスペンダーではないことを確認し、その後の署名検証を行います。
トークンの所有者がコントラクトアドレスの場合、ERC1271インターフェースによる署名検証を行い、正当な署名かどうかを確認します。
所有者がEOA(外部所有者アドレス)の場合、ECDSAの署名検証を行い、署名が正当かどうかを確認します。
最終的に、スペンダーに対するトークンの許可を_approve関数を介して設定します。

引数

  • spender
    • トークンの使用を許可されるアドレス。
  • tokenId
    • 許可するトークンの識別子。
  • deadline
    • 許可の有効期限のタイムスタンプ。
  • v
    • 署名のv値。
  • r
    • 署名のr値。
  • s
    • 署名のs値。

PeripheryValidation

checkDeadline

checkDeadline
modifier checkDeadline(uint256 deadline) {
    require(_blockTimestamp() <= deadline, 'Transaction too old');
    _;
}

概要
タイムスタンプの有効期限を確認する修飾子。

詳細
関数の実行前に _blockTimestamp()を使用して現在のブロックタイムスタンプを取得し、そのタイムスタンプが指定された有効期限 (deadline) より前であることを確認します。
もしタイムスタンプが有効期限以前であれば、関数の実行を許可します(_ により実際の関数の実行が行われます)。
タイムスタンプが有効期限を過ぎている場合は、エラーメッセージ**"Transaction too old"** とともに関数の実行が中止されます。

パラメータ

  • deadline
    • タイムスタンプの有効期限。
    • 関数内で現在のブロックタイムスタンプと比較されます。

SelfPermit

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

https://zenn.dev/cryptogames/articles/197e0f1ed053ac#selfpermit

PoolInitializer

createAndInitializePoolIfNecessary

createAndInitializePoolIfNecessary
function createAndInitializePoolIfNecessary(
    address token0,
    address token1,
    uint24 fee,
    uint160 sqrtPriceX96
) external payable override returns (address pool) {
    require(token0 < token1);
    pool = IUniswapV3Factory(factory).getPool(token0, token1, fee);

    if (pool == address(0)) {
        pool = IUniswapV3Factory(factory).createPool(token0, token1, fee);
        IUniswapV3Pool(pool).initialize(sqrtPriceX96);
    } else {
        (uint160 sqrtPriceX96Existing, , , , , , ) = IUniswapV3Pool(pool).slot0();
        if (sqrtPriceX96Existing == 0) {
            IUniswapV3Pool(pool).initialize(sqrtPriceX96);
        }
    }
}

概要
Uniswap V3のプールを必要に応じて作成し、初期化する関数。

詳細
トークンtoken0token1、手数料率 fee、および初期のプライス sqrtPriceX96 を受け取ります。
まず、token0token1より小さいことを確認します。
その後、ファクトリーコントラクトからプールのアドレスを取得します。

もし取得したプールのアドレスがゼロアドレス(アドレスが未割り当て)であれば、新しいプールを作成し初期化します。
既存のプールがある場合は、そのプールのスロット情報から現在のプライsqrtPriceX96Existingを取得し、ゼロであるかどうかを確認します。
もしゼロであれば、プールを初期化します。

引数

  • token0
    • トークン0のアドレス。
  • token1
    • トークン1のアドレス。
  • fee
    • 手数料率。
  • sqrtPriceX96
    • 初期のプライスの平方根(X96)。

戻り値

  • pool
    • 作成または初期化されたプールのアドレス。

PeripheryPayments

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

https://zenn.dev/cryptogames/articles/614571336c74e2#peripherypayments

BlockTimestamp

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

https://zenn.dev/cryptogames/articles/614571336c74e2#blocktimestamp

PositionKey

compute

compute
function compute(
    address owner,
    int24 tickLower,
    int24 tickUpper
) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(owner, tickLower, tickUpper));
}

概要
与えられたオーナーアドレスと2つの整数値(tickLowertickUpper)を元に、ハッシュ値を計算して返す関数。

詳細
3つの引数を取ります。
ownerはアドレス変数であり、ユーザーのアカウントアドレスを表します。
tickLowertickUpperint24型の整数変数であり、価格レンジを示す値です。
これらの引数をabi.encodePackedを使用してエンコードし、keccak256ハッシュ関数を適用することで、一意のハッシュ値が計算されます。

引数

  • owner
    • ハッシュ計算に使用されるオーナーアドレス。
  • tickLower
    • 価格レンジの下限を示す整数値。
  • tickUpper
    • 価格レンジの上限を示す整数値。

戻り値
計算されたハッシュ値(bytes32型)。


PoolAddress

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

https://zenn.dev/cryptogames/articles/197e0f1ed053ac#pooladdress

CallbackValidation

verifyCallback

verifyCallback
function verifyCallback(
    address factory,
    address tokenA,
    address tokenB,
    uint24 fee
) internal view returns (IUniswapV3Pool pool) {
    return verifyCallback(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee));
}

概要
Uniswap V3プールの正当性を検証し、そのプールのコントラクトアドレスを取得する関数。
与えられた情報を元に、プールの識別キーを取得し、それを使用してプールのコントラクトアドレスを計算して検証します。

詳細
この関数は、4つの引数を受け取ります。
factoryUniswap V3ファクトリーコントラクトのアドレスを示し、tokenAtokenBはトークン0およびトークン1のアドレスを示します。
feeはスワップごとにプールで集められる手数料率を表します。

この関数内で、PoolAddress.getPoolKey関数を呼び出して、与えられた情報からプールの識別キーを取得します。
その後、取得した識別キーを使用して、別のverifyCallback関数を呼び出します。
この2番目の関数では、PoolAddress.computeAddress関数を使ってプールのコントラクトアドレスを計算し、そのアドレスを返します。

引数

  • factory
    • Uniswap V3ファクトリーコントラクトのアドレス。
  • tokenA
    • トークン0またはトークン1のアドレス。
  • tokenB
    • もう片方のトークンのアドレス。
  • fee
    • プール内の各スワップに対する手数料率。

戻り値
正当性が検証されたUniswap V3プールのコントラクトアドレス(IUniswapV3Poolインターフェース)を返します。


verifyCallback

verifyCallback
function verifyCallback(address factory, PoolAddress.PoolKey memory poolKey)
    internal
    view
    returns (IUniswapV3Pool pool)
{
    pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
    require(msg.sender == address(pool));
}

概要
Uniswap V3プールの正当性を検証し、そのプールのコントラクトアドレスを取得する関数。
与えられたプールの識別キーを使用して、プールのコントラクトアドレスを計算し、正当性を検証します。

詳細
この関数は2つの引数を受け取ります。
factoryUniswap V3ファクトリーコントラクトのアドレスを示し、poolKeyはプールの識別キーを示す構造体です。

関数内で、PoolAddress.computeAddress関数を呼び出して、与えられたファクトリーアドレスとプールの識別キーを使用してプールのコントラクトアドレスを計算します。
その後、計算されたプールのアドレスと、呼び出し元(msg.sender)が一致することを確認するためにrequireステートメントを使用します。

引数

  • factory
    • Uniswap V3ファクトリーコントラクトのアドレス。
  • poolKey
    • プールの識別キー。

LiquidityAmounts

toUint128

toUint128
function toUint128(uint256 x) private pure returns (uint128 y) {
    require((y = uint128(x)) == x);
}

概要
uint256型の値をuint128型にダウンキャストする関数。

詳細
引数として与えられたuint256型の値をuint128型にダウンキャストします。
ダウンキャスト後、元の値とダウンキャスト後の値が等しいことを確認します。
もし等しくない場合、require文が失敗して関数の実行が中断されます。

引数

  • x
    • uint256型にダウンキャストする値。

戻り値

  • y
    • ダウンキャストされたuint128型の値。

getLiquidityForAmount0

getLiquidityForAmount0
function getLiquidityForAmount0(
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint256 amount0
) internal pure returns (uint128 liquidity) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
    uint256 intermediate = FullMath.mulDiv(sqrtRatioAX96, sqrtRatioBX96, FixedPoint96.Q96);
    return toUint128(FullMath.mulDiv(amount0, intermediate, sqrtRatioBX96 - sqrtRatioAX96));
}

概要
指定されたトークン0の量と価格範囲に基づいて受け取る流動性の量を計算する関数。

詳細
指定された2つの価格境界sqrtRatioAX96sqrtRatioBX96の間の流動性量を計算します。
計算は以下の式に基づいて行われます。

amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))

ここで、sqrt(upper)sqrt(lower)は価格の平方根を表し、amount0は送信されるトークン0の量です。

引数

  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • amount0
    • 送信されるトークン0の量。

戻り値

  • liquidity:計算された流動性の量。

getLiquidityForAmount1

getLiquidityForAmount1
function getLiquidityForAmount1(
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint256 amount1
) internal pure returns (uint128 liquidity) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
    return toUint128(FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtRatioBX96 - sqrtRatioAX96));
}

概要
指定されたトークン1の量と価格範囲に基づいて受け取る流動性の量を計算する関数。

詳細
指定された2つの価格境界sqrtRatioAX96sqrtRatioBX96の間の流動性量を計算します。
計算は以下の式に基づいて行われます。

amount1 / (sqrt(upper) - sqrt(lower))

ここで、sqrt(upper)sqrt(lower)は価格の平方根を表し、amount1は送信されるトークン1の量です。

引数

  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • amount1
    • 送信されるトークン1の量。

戻り値

  • liquidity
    • 計算された流動性の量。

getLiquidityForAmounts

getLiquidityForAmounts
function getLiquidityForAmounts(
    uint160 sqrtRatioX96,
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint256 amount0,
    uint256 amount1
) internal pure returns (uint128 liquidity) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

    if (sqrtRatioX96 <= sqrtRatioAX96) {
        liquidity = getLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0);
    } else if (sqrtRatioX96 < sqrtRatioBX96) {
        uint128 liquidity0 = getLiquidityForAmount0(sqrtRatioX96, sqrtRatioBX96, amount0);
        uint128 liquidity1 = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioX96, amount1);

        liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
    } else {
        liquidity = getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1);
    }
}

概要
指定されたトークン0の量、トークン1の量、現在のプール価格、および価格境界に基づいて受け取る最大流動性の量を計算する関数。

詳細
指定された3つの価格境界sqrtRatioX96sqrtRatioAX96sqrtRatioBX96の間で受け取る最大流動性量を計算します。
計算の方法は以下の通りです。

  • もしsqrtRatioX96sqrtRatioAX96以下であれば、getLiquidityForAmount0関数を使用して流動性を計算します。
  • もしsqrtRatioX96sqrtRatioAX96より大きく、sqrtRatioX96sqrtRatioBX96未満であれば、getLiquidityForAmount0関数とgetLiquidityForAmount1関数を組み合わせて最大の流動性を計算します。
  • もしsqrtRatioX96sqrtRatioBX96以上であれば、getLiquidityForAmount1関数を使用して流動性を計算します。

引数

  • sqrtRatioX96
    • 現在のプール価格を表す平方根価格。
  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • amount0
    • 送信されるトークン0の量。
  • amount1
    • 送信されるトークン1の量。

戻り値

  • liquidity
    • 計算された最大流動性の量。

getAmount0ForLiquidity

getAmount0ForLiquidity
function getAmount0ForLiquidity(
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint128 liquidity
) internal pure returns (uint256 amount0) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

    return
        FullMath.mulDiv(
            uint256(liquidity) << FixedPoint96.RESOLUTION,
            sqrtRatioBX96 - sqrtRatioAX96,
            sqrtRatioBX96
        ) / sqrtRatioAX96;
}

概要
指定された流動性の量と価格範囲に基づいて、トークン0の量を計算する関数。

詳細
指定された価格境界sqrtRatioAX96sqrtRatioBX96の間で受け取る流動性量に基づいて、トークン0の量を計算します。
計算は以下の式に基づいて行われます。

(amount0 << 96) * (sqrt(upper) - sqrt(lower)) / sqrt(upper)

ここで、amount0は計算されるトークン0の量、sqrt(upper)は価格範囲の上限の平方根価格、sqrt(lower)は価格範囲の下限の平方根価格です。

引数

  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • liquidity
    • 計算される流動性の量。

戻り値

  • amount0
    • 計算されたトークン0の量。

getAmount1ForLiquidity

getAmount1ForLiquidity
function getAmount1ForLiquidity(
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint128 liquidity
) internal pure returns (uint256 amount1) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

    return FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
}

概要
指定された流動性の量と価格範囲に基づいて、トークン1の量を計算する関数。

詳細
指定された価格境界sqrtRatioAX96sqrtRatioBX96の間で受け取る流動性量に基づいて、トークン1の量を計算します。
計算は以下の式に基づいて行われます。

liquidity * (sqrt(upper) - sqrt(lower)) / Q96

ここで、liquidityは計算される流動性の量、sqrt(upper)は価格範囲の上限の平方根価格、sqrt(lower)は価格範囲の下限の平方根価格です。

引数

  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • liquidity
    • 計算される流動性の量。

戻り値

  • amount1
    • 計算されたトークン1の量。

getAmountsForLiquidity

getAmountsForLiquidity
function getAmountsForLiquidity(
    uint160 sqrtRatioX96,
    uint160 sqrtRatioAX96,
    uint160 sqrtRatioBX96,
    uint128 liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
    if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);

    if (sqrtRatioX96 <= sqrtRatioAX96) {
        amount0 = getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
    } else if (sqrtRatioX96 < sqrtRatioBX96) {
        amount0 = getAmount0ForLiquidity(sqrtRatioX96, sqrtRatioBX96, liquidity);
        amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioX96, liquidity);
    } else {
        amount1 = getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, liquidity);
    }
}

概要
指定された流動性の量と価格範囲、および現在のプール価格に基づいて、トークン0とトークン1の量を計算する関数。

詳細
指定された3つの価格境界sqrtRatioX96sqrtRatioAX96sqrtRatioBX96の間で受け取る流動性量に基づいて、トークン0とトークン1の量を計算します。
計算の方法は以下の通りです。

  • もしsqrtRatioX96sqrtRatioAX96以下であれば、getAmount0ForLiquidity関数を使用してトークン0の量を計算します。
  • もしsqrtRatioX96sqrtRatioAX96より大きく、かつsqrtRatioX96sqrtRatioBX96未満であれば、getAmount0ForLiquidity関数とgetAmount1ForLiquidity関数を組み合わせてトークン0とトークン1の量を計算します。
  • もしsqrtRatioX96sqrtRatioBX96以上であれば、getAmount1ForLiquidity関数を使用してトークン1の量を計算します。

引数

  • sqrtRatioX96
    • 現在のプール価格を表す平方根価格。
  • sqrtRatioAX96
    • 最初の価格境界を表す平方根価格。
  • sqrtRatioBX96
    • 2番目の価格境界を表す平方根価格。
  • liquidity
    • 計算される流動性の量。

戻り値

  • amount0
    • 計算されたトークン0の量。
  • amount1
    • 計算されたトークン1の量。

TransferHelper

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

https://zenn.dev/cryptogames/articles/197e0f1ed053ac#transferhelper

ChainId

get

get
function get() internal pure returns (uint256 chainId) {
    assembly {
        chainId := chainid()
    }
}

概要
現在のチェーンの識別子(Chain ID)を取得する関数。

詳細
アセンブリ言語を使用して、Ethereumネットワーク上で実行されているチェーンの識別子(Chain ID)を取得します。
Chain ID は、異なるEthereumネットワークやサブネットワークを区別するために使用されます。

戻り値

  • chainId
    • 現在のチェーンの識別子。

注意
この関数は、アセンブリ言語を使用してChain IDを取得しています。
アセンブリ言語は低レベルのプログラミング言語であり、直接コンピュータの命令を制御するためのものです。

最後に

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

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

https://chaldene.net/

https://qiita.com/cardene

DeCipher |"Read me" for All of Contracts

Discussion