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

> Uniswap v3プロトコルの中心的な要素であるNonfungiblePositionManager（非代替ポジションマネージャー）コントラクトは、Uniswap v3の流動性プール内に存在する特別なポジションを管理するのが役割です。

- URL: https://cardene.dev/articles/96c6fa9d04460c
- 公開日: 2023-09-01
- 更新日: 2023-09-01
- トピック: Blockchain
- タグ: solidity, ethereum, smartcontract, defi

# はじめに

初めまして。
**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

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

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

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

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

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

# 概要

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

:::message
**特別なポジション**
**Uniswap v3**の流動性プール内での特定の価格範囲に関連付けられた非代替性のあるポジションを指します。
**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**コントラクトのアドレスです。
**WETH9**は**Wrapped Ether** の略で、Ethereumのイーサー（**ETH**）をEthereumの**ERC20**トークンである**WETH**として表現したものです。
これにより、イーサーを他のトークンと同じように取り扱えるようになります。

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

# コントラクト

## NonfungiblePositionManager

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

### Position

:::details Position
```solidity
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。
- `tickLower`、`tickUpper`
	- ポジションの価格範囲を示すティックの範囲。
- `liquidity`
	- ポジションの流動性（供給量）。
- `feeGrowthInside0LastX128`、`feeGrowthInside1LastX128`
	- 個別のポジションの最後のアクション時点での集計ポジションの手数料成長率。
- `tokensOwed0`、`tokensOwed1`
	- 最後の計算時点でポジションに支払われるトークン。

:::message
**手数料成長率（fee growth rate）**
**Uniswap V3**などの流動性プロトコルで使用される概念で、取引手数料がどれだけ蓄積されているかを表す指標です。
流動性プール内でトレードが行われるたびに、そのプールに蓄積された手数料が増加していきます。
手数料成長率は、その蓄積された手数料の増加率を示すものです。

1. **手数料蓄積**
プール内でトレードが行われると、そのトレードから発生する取引手数料がプールに蓄積されます。
この蓄積された手数料は、プール内の各ポジションの手数料成長率に影響を与えます。

2. **ポジションの手数料成長率**
各ポジション（流動性プール内の特定のトークンの所持状況）は、そのポジションの手数料成長率を保持しています。
ポジションの手数料成長率は、そのポジションが所有されている間に、ポジションが受ける手数料の増加率を表します。

3. **手数料成長率の更新**
ポジション内の手数料成長率は、新しいトレードが行われるたびに更新されます。
具体的には、前回のトレードからの手数料の増加量に基づいて手数料成長率が計算され、ポジションに格納されます。

手数料成長率は、ポジションの所有者が手数料を収集する際に重要な役割を果たします。
ポジションの手数料成長率に基づいて、ポジションが受け取る手数料の量が計算されます。
そのため、手数料成長率はプール内のトレードの影響を反映し、ポジションの価値を評価する際に重要な指標となります。
:::

### _poolIds

```solidity
mapping(address => uint80) private _poolIds;
```

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

### _poolIdToPoolKey

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

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

### _positions

```solidity
mapping(uint256 => Position) private _positions;
```

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

### _nextId

```solidity
uint176 private _nextId = 1;
```

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

### _nextPoolId

```solidity
uint80 private _nextPoolId = 1;
```

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

### _tokenDescriptor

```solidity
address private immutable _tokenDescriptor;
```

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

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

---

### positions

:::details positions
```solidity
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

:::details cachePoolKey
```solidity
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

:::details mint
```solidity
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

:::details increaseLiquidity
```solidity
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

:::details decreaseLiquidity
```solidity
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

:::details tokenURI
```solidity
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

:::details baseURI
```solidity
function baseURI() public pure override returns (string memory) {}
```
:::

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

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

**戻り値**
- `string`
	- ベースURI。

---

## isAuthorizedForToken

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

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

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

**パラメータ**
- `tokenId`
	- 関数がチェックする対象のトークンのID。

---

### collect 関数

:::details collect
```solidity
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

:::details burn
```solidity
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

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

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

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

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

**戻り値**
- `uint256`
	- 取得された`nonce`。

---

### getApproved

:::details getApproved
```solidity
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

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

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

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

**引数**
- `to`
	- 承認するアドレス。
- `tokenId`
	- 承認するトークンのトークンID。

**戻り値**
- なし

---

## LiquidityManagement

### MintCallbackData

:::details MintCallbackData
```solidity
struct MintCallbackData {
    PoolAddress.PoolKey poolKey;
    address payer;
}
```
:::

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

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

### uniswapV3MintCallback

:::details uniswapV3MintCallback
```solidity
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

:::details AddLiquidityParams
```solidity
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

:::details addLiquidity
```solidity
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

:::details _getAndIncrementNonce
```solidity
function _getAndIncrementNonce(uint256 tokenId) internal virtual returns (uint256);
```
:::

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

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

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

**戻り値**
- `uint256`
	- 取得した`nonce`値を返します。
	- 元の`nonce`値は1増加しています。

---

### nameHash, versionHash

:::details コンストラクタ
```solidity
bytes32 private immutable nameHash;
bytes32 private immutable versionHash;
```
:::

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

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

---

### constructor

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

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

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

**引数**
- `name_`
	- トークンの名前。
- `symbol_`
	- トークンのシンボル。
- `version_`
	- トークンのバージョン情報。

---

### DOMAIN_SEPARATOR

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

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

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

---

### PERMIT_TYPEHASH

```solidity
bytes32 public constant override PERMIT_TYPEHASH =
        0x49ecf333e5b8c95c40fdafc95c1ad136e8914a8fb55e9dc8bb01eaa83a2df9ad;
```

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

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

---

### permit

:::details permit
```solidity
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

:::details checkDeadline
```solidity
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

:::details createAndInitializePoolIfNecessary
```solidity
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**のプールを必要に応じて作成し、初期化する関数。

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

もし取得したプールのアドレスがゼロアドレス（アドレスが未割り当て）であれば、新しいプールを作成し初期化します。
既存のプールがある場合は、そのプールのスロット情報から現在のプライ`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

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

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

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

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

**戻り値**
計算されたハッシュ値（bytes32型）。

---

## PoolAddress

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

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

## CallbackValidation

### verifyCallback

:::details verifyCallback
```solidity
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つの引数を受け取ります。
`factory`は**Uniswap V3**ファクトリーコントラクトのアドレスを示し、`tokenA`と`tokenB`はトークン0およびトークン1のアドレスを示します。
`fee`はスワップごとにプールで集められる手数料率を表します。

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

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

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

---

### verifyCallback

:::details verifyCallback
```solidity
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つの引数を受け取ります。
`factory`は**Uniswap V3**ファクトリーコントラクトのアドレスを示し、`poolKey`はプールの識別キーを示す構造体です。

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

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

---

## LiquidityAmounts

### toUint128

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

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

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

**引数**
- `x`
	- `uint256`型にダウンキャストする値。

**戻り値**
- `y`
	- ダウンキャストされた`uint128`型の値。

### getLiquidityForAmount0

:::details getLiquidityForAmount0
```solidity
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つの価格境界`sqrtRatioAX96`と`sqrtRatioBX96`の間の流動性量を計算します。
計算は以下の式に基づいて行われます。

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

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

**戻り値**
- `liquidity`：計算された流動性の量。

:::message
例として、以下の値を用いて計算を行ってみましょう。

sqrtRatioAX96：`1000000000000000000000000`
sqrtRatioBX96：`1200000000000000000000000`
amount0：`50000000000000000000`
まず、価格範囲の上限と下限を計算します。
```
sqrt(upper)：sqrtRatioBX96 = 1200000000000000000000000
sqrt(lower)：sqrtRatioAX96 = 1000000000000000000000000
``

次に、中間値`intermediate`を計算します。

```
intermediate = sqrt(upper) * sqrt(lower) / 2^96
= 1200000000000000000000000 * 1000000000000000000000000 / 2^96
≈ 1154700533444385611846965.7895
```

最後に、流動性`liquidity`を計算します。

```
liquidity = amount0 * intermediate / (sqrt(upper) - sqrt(lower))
= 50000000000000000000 * 1154700533444385611846965.7895 / (1200000000000000000000000 - 1000000000000000000000000)
≈ 9.7132256441937224018576820493
```

計算結果から、指定された条件でトークン0の量が`50000000000000000000`の場合、約`9.71`の流動性が受け取られることがわかります。
:::

### getLiquidityForAmount1

:::details getLiquidityForAmount1
```solidity
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つの価格境界`sqrtRatioAX96`と`sqrtRatioBX96`の間の流動性量を計算します。
計算は以下の式に基づいて行われます。

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

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

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

**戻り値**
- `liquidity`
	- 計算された流動性の量。

### getLiquidityForAmounts

:::details getLiquidityForAmounts
```solidity
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つの価格境界`sqrtRatioX96`、`sqrtRatioAX96`、`sqrtRatioBX96`の間で受け取る最大流動性量を計算します。
計算の方法は以下の通りです。

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

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

**戻り値**
- `liquidity`
	- 計算された最大流動性の量。

---

### getAmount0ForLiquidity

:::details getAmount0ForLiquidity
```solidity
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の量を計算する関数。

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

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

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

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

**戻り値**
- `amount0`
	- 計算されたトークン0の量。

### getAmount1ForLiquidity

:::details getAmount1ForLiquidity
```solidity
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の量を計算する関数。

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

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

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

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

**戻り値**
- `amount1`
	- 計算されたトークン1の量。

### getAmountsForLiquidity

:::details getAmountsForLiquidity
```solidity
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つの価格境界`sqrtRatioX96`、`sqrtRatioAX96`、`sqrtRatioBX96`の間で受け取る流動性量に基づいて、トークン0とトークン1の量を計算します。
計算の方法は以下の通りです。

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

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

**戻り値**
- `amount0`
	- 計算されたトークン0の量。
- `amount1`
	- 計算されたトークン1の量。


## TransferHelper

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

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

## ChainId

### get

:::details get
```solidity
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
