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

> Uniswap V3プロトコルの一部であるV3Migratorコントラクトは、、ユーザーが以前のUniswap V2の流動性を新しいUniswap V3に移すのを支援する役割を果たしています。

- URL: https://cardene.dev/articles/197e0f1ed053ac
- 公開日: 2023-08-31
- 更新日: 2023-08-31
- トピック: 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/0xa5644e29708357803b5a882d272c41cc0df92b34

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

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

# 概要

**Uniswap V3**プロトコルの一部である**V3Migrator**コントラクトは、、ユーザーが以前の**Uniswap V2**の流動性を新しい**Uniswap V3**に移すのを支援する役割を果たしています。

:::message
**Uniswap V1**から**Uniswap V2**への流動性の移行については以下にまとめられています。
:::

https://zenn.dev/cryptogames/articles/644169eae6e90c

主な役割としては以下の点が挙げられます。

1. **Uniswap V3**の中核となるコントラクトと連携し、古い**Uniswap V2**から新しい**Uniswap V3**への流動性の移行を処理します。
これにより、ユーザーは新しいプロトコルを利用しながら、以前の流動性を活用できます。
2. **LowGasSafeMath**ライブラリを活用して、数学的な計算を効率的かつガス消費の少ない方法で行い、コストを抑えつつ安全な操作が実現されます。
3. **Uniswap V2**のペアコントラクトと連携し、**Uniswap V2**のプロトコルから流動性情報を取得し、新しいプロトコルに移すための処理を行います。
4. **NonfungiblePositionManager**コントラクトと連携して、移行中の流動性ポジションを管理・操作します。
5. **TransferHelper**ライブラリを使用して、トークンの転送を安全に行い、トークン移動に関するリスクを最小限に抑えます。
7. **IV3Migrator**インターフェースを実装することで、他のコントラクトとの互換性と標準化が確保されます。
これにより、異なるコントラクト間での連携がスムーズに行えます。
8. さまざまなベースコントラクトからの機能を取り入れることで、**V3Migrator**コントラクトの機能性とセキュリティが向上し、安全かつ強力な機能が提供されます。
10. 外部コントラクトと連携し、異なるトークン規格に対応するためのインターフェースを提供し、さまざまなトークンを移行プロセスで扱えるようになります。

# 使い方

**V3Migrator**コントラクトは、**Uniswap V2**から**Uniswap V3**への流動性の移行をサポートするスマートコントラクトです。
同時に、移行された流動性の管理や操作にも関する便利な機能を提供します。
これにより、流動性プロバイダーは、**Uniswap V3**が提供する集中型の流動性や複数の手数料レベルといった強化された特徴を活用できます。

## 手順
具体的な手順は以下の通りです。

1. 流動性移行を管理するためのスマートコントラクトである**V3Migrator**コントラクトをデプロイします。
2. コントラクトを必要なパラメータで初期化します。
3. **Uniswap V2**のプールから流動性トークンを**V3Migrator**コントラクトが使用できるように承認し、トークンの移行を可能にします。
5. **V3Migrator**コントラクト上で移行関数を呼び出し、流動性移行が開始されます。
6. 移行プロセスを監視し、イベントやエラーを処理しながら、移行がスムーズに進行することを確認します。
7. 移行された流動性を**Uniswap V3**のプールで操作するために、提供された関数やイベントを使用し、新しいプールでの流動性の管理が可能になります。

## 関数呼び出し
**Uniswap V2**から**Uniswap V3**への流動性移行には、以下の関数を呼び出す必要があります。

### constructor
**V3Migrator**コントラクトを必要なパラメータで初期化します。

### approve
**Uniswap V2**プールから、**V3Migrator**コントラクトが指定した量の流動性トークンを使用できるように承認し、流動性を移行する準備を整えます。

### migrate
**Uniswap V2**から**Uniswap V3**へ指定された流動性を移行します。

## 使い方

**Uniswap V2**から**Uniswap V3**への流動性の移行。

1. **V3Migrator**コントラクトをデプロイします。
2. コントラクトを必要なパラメータで初期化します。
3. **Uniswap V2**のプールから、移行する流動性トークンの指定された量を**V3Migrator**コントラクトが使用できるように承認します。
4. **V3Migrator**コントラクト上で`migrate`関数を呼び出し、移行に必要なパラメータを提供し、**Uniswap V2**から**V3**への流動性移行が実行されます。

**Uniswap V3**で移行された流動性とのやり取り。

1. 提供された関数を使用して、**Uniswap V3**プール内の移行された流動性とやり取りし、新しいプール内での流動性の操作を可能にします。
2. **V3Migrator**コントラクトが発行するイベントを監視し、移行された流動性に関するアップデートや変更に適切に対処します。

## 関連EIP/ERC

- ERC20: ファンジブルトークンの標準インターフェースを定義。
- ERC721: ノンファンジブルトークンの標準インターフェースを定義。
- ERC165: コントラクトのインスペクションのための標準インターフェースを定義。
- ERC721Metadata: 追加のメタデータ関数を持つ**ERC721**インターフェースを拡張。
- ERC721Enumerable: 追加の列挙関数を持つ**ERC721**インターフェースを拡張。
- ERC20Permit: パーミット機能を持つ**ERC20**トークンの標準インターフェースを定義。
- IUniswapV2Pair: **Uniswap V2**ペアコントラクトのインターフェース。
- INonfungiblePositionManager: **Uniswap V3**非ファンジブルポジションマネージャーコントラクトのインターフェース。
- IUniswapV3Factory: **Uniswap V3**ファクトリーコントラクトのインターフェース。
- IUniswapV3Pool: **Uniswap V3**プールコントラクトのインターフェース。
- IUniswapV3PoolImmutables: **Uniswap V3**プールの不変データのインターフェース。
- IUniswapV3PoolState: **Uniswap V3**プールの状態のインターフェース。
- IUniswapV3PoolDerivedState: **Uniswap V3**プールの派生状態のインターフェース。
- IUniswapV3PoolActions: **Uniswap **V3プールのアクションのインターフェース。
- IUniswapV3PoolOwnerActions: **Uniswap V3**プールのオーナーアクションのインターフェース。
- IUniswapV3PoolEvents: **Uniswap V3**プールのイベントのインターフェース。

# パラメーター

### _factory
**Uniswap V3**のファクトリーコントラクトのアドレス。
このコントラクトは**Uniswap V3**のプールを生成する役割を担っています。
つまり、新しいトレーディングペアのプールを作成する際にこのアドレスを参照します。

### _WETH9
**WETH9**コントラクトのアドレスを指します。
**WETH（Wrapped Ether）** は、Ethereumのネイティブトークンである**Ether（ETH）** をEthereumのスマートコントラクトでトレードや操作するためにラップしたものです。
このアドレスは**WETH9**トークンの操作に使用されます。

### _nonfungiblePositionManager
非ファンジブルポジションマネージャーコントラクトのアドレス。
**Uniswap V3**の非ファンジブルポジションマネージャーコントラクトは、ユーザーが**Uniswap V3**のポジションを管理し、移動するための機能を提供します。
このアドレスはそのコントラクトの操作に使用されます。

# コントラクト

## V3Migrator

### nonfungiblePositionManager

:::details nonfungiblePositionManager
```solidity
address public immutable nonfungiblePositionManager;
```
:::

**概要**
非ファンジブルポジションマネージャーコントラクトのアドレス。

**詳細**
**Uniswap V3**の非ファンジブルポジションマネージャーコントラクトのアドレスを格納します。
非ファンジブルポジションマネージャーコントラクトは、**Uniswap V3**の流動性ポジションを管理するための機能を提供します。
`immutable`が使用されているため、コントラクトのデプロイ後に値を変更することはできません。

### receive

:::details receive
```solidity
receive() external payable {
    require(msg.sender == WETH9, 'Not WETH9');
}
```
:::

**概要**
コントラクトが**ETH**を受け取る際に呼び出される関数。

**詳細**
**ETH**がコントラクトに送信される際に自動的に呼び出される関数。
関数内で、送信者が**WETH9**コントラクトであることを確認し、それ以外のアドレスからの**ETH**送信を拒否します。

### migrate

:::details migrate
```solidity
function migrate(MigrateParams calldata params) external override {
        require(params.percentageToMigrate > 0, 'Percentage too small');
        require(params.percentageToMigrate <= 100, 'Percentage too large');

        // burn v2 liquidity to this address
        IUniswapV2Pair(params.pair).transferFrom(msg.sender, params.pair, params.liquidityToMigrate);
        (uint256 amount0V2, uint256 amount1V2) = IUniswapV2Pair(params.pair).burn(address(this));

        // calculate the amounts to migrate to v3
        uint256 amount0V2ToMigrate = amount0V2.mul(params.percentageToMigrate) / 100;
        uint256 amount1V2ToMigrate = amount1V2.mul(params.percentageToMigrate) / 100;

        // approve the position manager up to the maximum token amounts
        TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, amount0V2ToMigrate);
        TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, amount1V2ToMigrate);

        // mint v3 position
        (, , uint256 amount0V3, uint256 amount1V3) =
            INonfungiblePositionManager(nonfungiblePositionManager).mint(
                INonfungiblePositionManager.MintParams({
                    token0: params.token0,
                    token1: params.token1,
                    fee: params.fee,
                    tickLower: params.tickLower,
                    tickUpper: params.tickUpper,
                    amount0Desired: amount0V2ToMigrate,
                    amount1Desired: amount1V2ToMigrate,
                    amount0Min: params.amount0Min,
                    amount1Min: params.amount1Min,
                    recipient: params.recipient,
                    deadline: params.deadline
                })
            );

        // if necessary, clear allowance and refund dust
        if (amount0V3 < amount0V2) {
            if (amount0V3 < amount0V2ToMigrate) {
                TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, 0);
            }

            uint256 refund0 = amount0V2 - amount0V3;
            if (params.refundAsETH && params.token0 == WETH9) {
                IWETH9(WETH9).withdraw(refund0);
                TransferHelper.safeTransferETH(msg.sender, refund0);
            } else {
                TransferHelper.safeTransfer(params.token0, msg.sender, refund0);
            }
        }
        if (amount1V3 < amount1V2) {
            if (amount1V3 < amount1V2ToMigrate) {
                TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, 0);
            }

            uint256 refund1 = amount1V2 - amount1V3;
            if (params.refundAsETH && params.token1 == WETH9) {
                IWETH9(WETH9).withdraw(refund1);
                TransferHelper.safeTransferETH(msg.sender, refund1);
            } else {
                TransferHelper.safeTransfer(params.token1, msg.sender, refund1);
            }
        }
}
```
:::

**概要**
**Uniswap V2**から**Uniswap V3**への流動性移行を実行する関数。

**詳細**
指定されたパラメータに基づいて**Uniswap V2**から**Uniswap V3**への流動性移行プロセスを実行します。
移行に必要なパラメータを受け取り、移行の実行前に一定のバリデーションを行います。
移行が完了すると、**Uniswap V3**の流動性が生成され、必要に応じてアセットの払い戻しも行われます。

## Multicall

### multicall

:::details multicall
```solidity
function multicall(bytes[] calldata data) external payable override returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; i++) {
        (bool success, bytes memory result) = address(this).delegatecall(data[i]);

        if (!success) {
            // Next 5 lines from https://ethereum.stackexchange.com/a/83577
            if (result.length < 68) revert();
            assembly {
                result := add(result, 0x04)
            }
            revert(abi.decode(result, (string)));
        }

        results[i] = result;
    }
}
```
:::

**概要**
複数のコントラクト関数を一括して呼び出すためのメカニズムを提供する関数。
指定されたデータの配列を使用して、デリゲートコールを行い、複数の関数呼び出しの結果を返します。

**詳細**
この関数は、複数のコントラクト関数を一括して呼び出すことができる便利な機能を提供します。`data`配列には、呼び出す関数のデータ（コールデータ）が含まれています。
関数呼び出しの結果は、`results`配列に格納されて返されます。
`delegatecall`を使用して関数を呼び出し、その結果と成功ステータスを取得します。

**引数**
- `data`
	- 関数呼び出しのためのコールデータの配列。

**戻り値**
- `results`
	- 各関数呼び出しの結果が格納されたバイトデータの配列。

## SelfPermit

### selfPermit

:::details selfPermit
```solidity
function selfPermit(
    address token,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) public payable override {
    IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s);
}
```
:::

**概要**
トークンの所有者が**SelfPermit**コントラクトに対してトークンの送付許可を与えるメカニズムを提供する関数。

**詳細**
トークンの所有者が署名したメッセージを使用して、トークンの送付許可を**SelfPermit**コントラクトに与えるために使用されます。

**引数**
- `token`
	- トークンのアドレス。
- `value`
	- 許可するトークンの数量。
- `deadline`
	- 署名の有効期限。
- `v`, `r`, `s`
	- 署名のパラメータ。

---

### selfPermitIfNecessary

:::details selfPermitIfNecessary
```solidity
function selfPermitIfNecessary(
    address token,
    uint256 value,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external payable override {
    if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s);
}
```
:::

**概要**
必要な場合にのみトークンの許可を**SelfPermit**コントラクトに与えるメカニズムを提供する関数。

**詳細**
トークンの許可が要求された数量を満たしていない場合にのみ、`selfPermit`関数を呼び出してトークンの送付許可を与えるために使用されます。
トークンの許可をチェックし、要求された数量を満たしていない場合には`selfPermit`関数を呼び出します。

**引数**
- `token`
	- トークンのアドレス。
- `value`
	- 許可するトークンの数量。
- `deadline`
	- 署名の有効期限。
- `v`, `r`, `s`
	- 署名のパラメータ。

---

### selfPermitAllowed

:::details selfPermitAllowed
```solidity
function selfPermitAllowed(
    address token,
    uint256 nonce,
    uint256 expiry,
    uint8 v,
    bytes32 r,
    bytes32 s
) public payable override {
    IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s);
}
```
:::

**概要**
トークンの所有者が送付許可を**SelfPermit**コントラクトに与えるための拡張メカニズムを提供する関数。

**詳細**
トークン所有者は、署名を使用して関数を呼び出すことによって、指定された条件でトークンの許可をコントラクトに与えることができます。

**引数**
- `token`
	- トークンのアドレス。
- `nonce`
	- トランザクションのユニーク性を確保するための数値。
- `expiry`
	- 署名の有効期限。
- `v`, `r`, `s`
	- 署名のパラメータ。

---

### selfPermitAllowedIfNecessary

:::details selfPermitAllowedIfNecessary
```solidity
function selfPermitAllowedIfNecessary(
    address token,
    uint256 nonce,
    uint256 expiry,
    uint8 v,
    bytes32 r,
    bytes32 s
) external payable override {
    if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max)
        selfPermitAllowed(token, nonce, expiry, v, r, s);
}
```
:::

**概要**
必要な場合にのみトークンの送付許可を**SelfPermit**コントラクトに与える拡張メカニズムを提供する関数。

**詳細**
トークンの許可がすでに最大値を超えていない場合にのみ、`selfPermitAllowed`関数を呼び出してトークンの許可を与えるために使用されます。
トークンの許可をチェックし、最大値を超えていない場合には`selfPermitAllowed`関数を呼び出します。

**引数**
- `token`
	- トークンのアドレス。
- `nonce`
	- トランザクションのユニーク性を確保するための数値。
- `expiry`
	- 署名の有効期限。
- `v`, `r`, `s`
	- 署名のパラメータ。

---

## 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**プールを作成および初期化するためのメカニズムを提供する関数。

**詳細**
この関数は、指定されたトークンと手数料率に対応する**Uniswap V3**プールが存在するかどうかをチェックし、存在しない場合は新しいプールを作成して初期化します。
既に存在するプールがある場合は、そのプールのスロット0情報を取得し、初期化が行われているかを確認します。
初期化が行われていない場合は、プールを初期化します。

**引数**
- `token0`
	- トークン0のアドレス。
- `token1`
	- トークン1のアドレス。
- `fee`
	- 手数料率。
- `sqrtPriceX96`
	- 初期の平方根プライス。

**戻り値**
- `pool`
	- 作成または初期化された**Uniswap V3**プールのアドレス。

:::message
`sqrtPriceX96` は、**Uniswap V3**プール内の価格情報を表す重要な値です。
**Uniswap V3**では、価格を`sqrtPriceX96`という形で表現しています。

### `sqrtPriceX96`とは何か？

`sqrtPriceX96`は、**Uniswap V3**プール内の価格情報を表す数値です。
具体的には、トークン0とトークン1の間の取引ペアの価格を表しています。
この値は、`X * Y = K`という定数乗算価格関数を使用して計算されます。
ここで、`X`と`Y`はトークン0とトークン1の残高であり、`K`は定数です。

`sqrtPriceX96`は、価格を平方根として表現したものです。
これは、価格が線形ではなく、価格帯ごとに定義される**Uniswap V3**の特性に適しています。

### `sqrtPriceX96`の意味と使い方

- `sqrtPriceX96`の値が高いほど、トークン0の価格がトークン1の価格に比べて高くなります。
- `sqrtPriceX96`の値が低いほど、トークン0の価格がトークン1の価格に比べて低くなります。
- プール内の価格変動は、`sqrtPriceX96`の変動によって示されます。
- 価格帯が変わると、`sqrtPriceX96`も変化します。

`sqrtPriceX96`は**Uniswap V3**プールの価格情報を表す数値であり、価格変動のトラッキングに使用されます。
プール内での取引価格を理解し、効果的な取引を行うために重要な指標となります。
:::

## TransferHelper

### safeTransferFrom

:::details safeTransferFrom
```solidity
function safeTransferFrom(
    address token,
    address from,
    address to,
    uint256 value
) internal {
    (bool success, bytes memory data) =
        token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
}
```
:::

**概要**
指定されたトークンを`from`アドレスから`to`アドレスに安全に転送する関数。

**詳細**
この関数では、`token`アドレスのトークンコントラクトに対して`IERC20.transferFrom`関数を呼び出してトークンを転送しています。
転送が成功したかどうかは`success`変数で確認し、転送が正常に行われなかった場合には、`STF`エラーが発生します。


**引数**
- `token`
	- トークンのコントラクトアドレス。
- `from`
	- トークンの転送元アドレス。
- `to`
	- トークンの転送先アドレス。
- `value`
	- 転送するトークンの量。


### safeTransfer

:::details safeTransfer
```solidity
function safeTransfer(
    address token,
    address to,
    uint256 value
) internal {
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
}
```
:::

**概要**
`msg.sender`から指定されたアドレス`to`にトークンを安全に転送する関数。

**詳細**
`token`アドレスのトークンコントラクトに対して`IERC20.transfer`関数を呼び出してトークンを転送しています。
転送が成功したかどうかは `success`変数で確認し、転送が正常に行われなかった場合には、`ST`エラーが発生します。

**引数**
- `token`
	- トークンのコントラクトアドレス。
- `to`
	- トークンの転送先アドレス。
- `value`
	- 転送するトークンの量。

### safeApprove

:::details safeApprove
```solidity
function safeApprove(
    address token,
    address to,
    uint256 value
) internal {
    (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
    require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
}
```
:::

**概要**
指定されたアドレス`to`に対してトークンの承認を安全に行う関数。

**詳細**
この関数では、`token`アドレスのトークンコントラクトに対して`IERC20.approve`関数を呼び出して承認を行っています。
承認が成功したかどうかは`success`変数で確認し、承認が正常に行われなかった場合には、`SA`エラーが発生します。

**引数 or パラメータ**
- `token`
	- トークンのコントラクトアドレス。
- `to`
	- 承認対象のアドレス。
- `value`
	- 承認するトークンの量。


### safeTransferETH

:::details safeTransferETH
```solidity
function safeTransferETH(address to, uint256 value) internal {
    (bool success, ) = to.call{value: value}(new bytes(0));
    require(success, 'STE');
}
```
:::

**概要**
指定されたアドレス`to`に**ETH**を安全に転送する関数。

**詳細**
この関数では、`to`アドレスに対して指定した量の**ETH**を転送しています。
転送が成功したかどうかは`success`変数で確認し、転送が正常に行われなかった場合には、`STE`エラーが発生します。

**引数**
- `to`
	- **ETH**の転送先アドレス。
- `value`
	- 転送する**ETH**の量。

## PoolAddress

### POOL_INIT_CODE_HASH

:::details POOL_INIT_CODE_HASH
```solidity
bytes32 internal constant POOL_INIT_CODE_HASH = 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
```
:::

**概要**
はプールのイニシャライズコードのハッシュ値を表す定数。

### PoolKey

:::details PoolKey
```solidity
struct PoolKey {
    address token0;
    address token1;
    uint24 fee;
}
```
:::

**概要**
プールの識別キーを表す構造体。
この構造体はプールのトークンと手数料レベルを格納します。

### getPoolKey

:::details getPoolKey
```solidity
function getPoolKey(
    address tokenA,
    address tokenB,
    uint24 fee
) internal pure returns (PoolKey memory) {
    if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
    return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
}
```
:::

**概要**
プールキーを取得する関数。
与えられたトークンと手数料レベルを元に、トークンを昇順に並べたプールキーを生成します。

**引数**
- `tokenA`
	- プールの最初のトークン。
- `tokenB`
	- プールの2番目のトークン。
- `fee`
	- プールの手数料レベル。

**戻り値**
- `PoolKey`
	- `PoolKey`構造体。
	- 昇順に並べられたトークンと手数料レベルが含まれます。

### computeAddress

:::details computeAddress
```solidity
function computeAddress(address factory, PoolKey memory key) internal pure returns (address pool) {
    require(key.token0 < key.token1);
    pool = address(
        uint256(
            keccak256(
                abi.encodePacked(
                    hex'ff',
                    factory,
                    keccak256(abi.encode(key.token0, key.token1, key.fee)),
                    POOL_INIT_CODE_HASH
                )
            )
        )
    );
}
```
:::

**概要**
プールのアドレスを計算する関数。
ファクトリーのアドレスとプールキーを元に、プールのアドレスを決定的に計算します。

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

**戻り値**
プールのアドレスを返します。

# イベント

なし。

# コード

## インターフェース

- `contracts/interfaces`

## ライブラリ

- `contracts/libraries`

## ベースコントラクト

- `contracts/base`

# 最後に

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

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

https://chaldene.net/

https://qiita.com/cardene
