# UniswapV3のドキュメントを読んでみよう！

> Uniswap V3は、スマートコントラクトを使用したプロトコルであり、分散型取引所（DEX）の一つです。このプロトコルは、ユーザーが異なるトークンを交換したり流動性を提供したりすることを可能にします。

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

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

今回は**Uniswap V3**について公式ドキュメントを翻訳・補足しながらまとめていきたいと思います。

公式ドキュメントは以下になります。

https://docs.uniswap.org/contracts/v3/overview

# 概要

**Uniswap V3**は、スマートコントラクトを使用したプロトコルであり、分散型取引所（DEX）の一つです。
このプロトコルは、ユーザーが異なるトークンを交換したり流動性を提供したりすることを可能にします。
**Uniswap V3**のスマートコントラクトに関するドキュメントによると、以下のような情報が提供されています。

## ガイド
**Uniswap V3**スマートコントラクトについて初めて学ぶ方は、基本的な概念から始めることをおすすめします。
これにより、プロトコルの基本的な動作原理や用語を理解できます。
その後、[ローカル環境をセットアップ](https://docs.uniswap.org/contracts/v3/guides/local-environment)し、[最初のトークンスワップ（交換）](https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps)を実行する方法について学ぶことができます。

## リファレンス
深く掘り下げて理解したい場合は、[技術リファレンスドキュメント](https://docs.uniswap.org/contracts/v3/reference/overview)を読むことができます。
ここでは、スマートコントラクトの詳細な仕様や関数、メソッドについて詳しく説明されています。

## リソース
V3コアおよびV3パリフェリを含む、さまざまなリソースが提供されています。
これらのリソースを通じて、さらに詳細な情報や実際のコード例を得ることができます。

**Uniswap V3**のスマートコントラクトは、Solidityなどの言語を使用して開発されています。
エンジニアとしてのスキルセットを活かして、これらのスマートコントラクトを理解し、開発に活用することができます。
コードの品質や可読性にも注意を払いつつ、新たな流動性戦略やトークン交換の仕組みを構築する際に役立ててください。

- [V3 Core](https://github.com/Uniswap/v3-core)
- [V3 Periphery](https://github.com/Uniswap/v3-periphery)

# ガイド

## スワップ

### シングルスワップ

**Uniswap**では、仮想通貨間のスワップ（通貨交換）を行うことができます。
例を通じて、**Uniswap**でスワップを行う2つの関数「`swapExactInputSingle`」と「`swapExactOutputSingle`」について説明していきます。

**swapExactInputSingle**
この関数は、特定の数量のトークンを別のトークンに交換する時に使います。
例えば、あなたが一定量の**ETH**を持っており、それを可能な限り多く別のトークンに交換したい場合に使います。
この関数では、交換の詳細を「`ExactInputSingleParams`」というデータで指定し、**Uniswap**の「**ISwapRouter**」インターフェース内の「`exactInputSingle`」関数を呼び出します。

**swapExactOutputSingle**
この関数は、特定の数量のトークンを欲しいトークンに交換する時に使います。
例えば、最低限のトークン量で別のトークンと交換したい場合に使います。
この関数では、交換の詳細を「`ExactOutputSingleParams`」というデータで指定し、、**Uniswap**の「**ISwapRouter**」インターフェース内の「`exactOutputSingle`」関数を呼び出します。

この例では、特定のトークンのアドレスを直接コードに組み込んでいますが、実際の取引ではトランザクションごとに異なるトークンやプールを選択でき、柔軟性を持たせられます。

スマートコントラクトから**Uniswap**を利用する場合、外部の価格情報が不可欠です。
これがないと、他のトレーダーによって取引が先取りされ、損失が生じる可能性があります。

:::message
この例は学習のためのものであり、実際に使用されているコードではありません。
:::

#### コントラクトのセットアップ

```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';

contract SwapExamples {
	// For the scope of these swap examples,
	// we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.
	// It should be noted that for the sake of these examples we pass in the swap router as a constructor argument instead of inheriting it.
	// More advanced example contracts will detail how to inherit the swap router safely.
	// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

	ISwapRouter public immutable swapRouter;

	address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
	address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
	address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

	// For this example, we will set the pool fee to 0.3%.
	uint24 public constant poolFee = 3000;

	constructor(ISwapRouter _swapRouter) {
		swapRouter = _swapRouter;
	}
	
	...
```

上記コントラクトでは、トークンのコントラクトアドレスとプール内の手数料をハードコードしています。

#### 正確なinputスワップ

スマートコントラクトからトークンを引き出してスワップを実行するには、以下のステップを踏みます。
以下のステップには、呼び出し元（ユーザー）からのトークン引き出しと**Uniswap**プロトコルのルーターコントラクトへのトークン承認が含まれます。

1. **トークン引き出しの承認**
まず、呼び出し元（ユーザー）は、**Uniswap**プロトコルのルーターコントラクトに対して、ユーザーが所有しているトークンの引き出しを許可する必要があります。
これにより、**Uniswap**プロトコルがユーザーが所有するトークンを引き出して使用できるようになります。

2. **Daiトークンの転送**
次に、呼び出し元（ユーザー）のアドレスから一定数量の**Dai**をコントラクトに転送します。
この際、転送する**Dai**の数量を「`amount`」として指定します。

```solidity
	...
	
	/// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9
	/// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
	/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
	/// @param amountIn The exact amount of DAI that will be swapped for WETH9.
	/// @return amountOut The amount of WETH9 received.
	function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
	// msg.sender must approve this contract

	// Transfer the specified amount of DAI to this contract.
	TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

	// Approve the router to spend DAI.
	TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
	
	...
```

##### パラメーター

スワップを実行するには、`ExactInputSingleParams`というデータを渡す必要があります。
以下はパラメータの説明です。

- **tokenIn（入力トークン）**
	- スワップの時に交換元として使用するトークンのコントラクトアドレス。
- **tokenOut（出力トークン）**
	- スワップの結果として得られるトークンのコントラクトアドレス。
- **fee（手数料ティア）**
	- スワップを実行する適切なプールコントラクトを特定する、プールの手数料情報。
- **recipient（受取アドレス）**
	- スワップによって得られたトークンが送信されるアドレス。
- **deadline（有効期限）**
	- スワップが失敗する`unix`時間。
	- これにより、トランザクションが長期間保留されたり、価格が急激に変動した場合にトランザクションが実行されないように保護します。
- **amountOutMinimum（最小出力トークン量）**
	- この値は`0`に設定されていますが、本番環境では非常に重要。
	- この値はSDKやオンチェーンの価格オラクルを使用して計算され、スワップによって得られるトークン量が一定の最小量以上であることを確認するために使用されます。
	- これにより、フロントランニングや価格操作による悪意ある価格での取引から保護されます。
- **sqrtPriceLimitX96**
	- この値も`0`に設定されていますが、本番環境では価格の制限を設定する。
	- 価格の影響を制御したり、価格に関連する様々なメカニズムを設定したりする時に役立ちます。

##### 関数呼び出し

```solidity
	...
	
	// Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
	// We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
	ISwapRouter.ExactInputSingleParams memory params =
		ISwapRouter.ExactInputSingleParams({
			tokenIn: DAI,
			tokenOut: WETH9,
			fee: poolFee,
			recipient: msg.sender,
			deadline: block.timestamp,
			amountIn: amountIn,
			amountOutMinimum: 0,
			sqrtPriceLimitX96: 0
		});

	// The call to `exactInputSingle` executes the swap.
	amountOut = swapRouter.exactInputSingle(params);
	}
	
	...
```

#### 正確なoutputスワップ

**Exact Output**スワップは、取得したいトークン数量と最低限の入力トークン量を交換します。

例えば、ある数量の特定のトークンを得る必要がある場合、**Exact Output**スワップを使用できます。
この場合、必要な出力トークン量を指定し、その出力量を確実に得るために最小でどれだけの入力トークンが必要かを計算します。
そして、その最小量以上の入力トークンを提供して、指定した出力量を確保します。

ただし、このスワップを使うと、スワップが実行された後に入力したトークンが余る可能性があるため、スワップの最後に余った入力トークンを呼び出し元のアドレスに返金します。
これにより、トークンの無駄を最小限に抑えることができます。

##### 関数呼び出し

```solidity
	...
	
	/// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.
	/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
	/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
	/// @param amountOut The exact amount of WETH9 to receive from the swap.
	/// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
	/// @return amountIn The amount of DAI actually spent in the swap.
	function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
	// Transfer the specified amount of DAI to this contract.
	TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);

	// Approve the router to spend the specified `amountInMaximum` of DAI.
	// In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
	TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

	ISwapRouter.ExactOutputSingleParams memory params =
		ISwapRouter.ExactOutputSingleParams({
			tokenIn: DAI,
			tokenOut: WETH9,
			fee: poolFee,
			recipient: msg.sender,
			deadline: block.timestamp,
			amountOut: amountOut,
			amountInMaximum: amountInMaximum,
			sqrtPriceLimitX96: 0
		});

	// Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
	amountIn = swapRouter.exactOutputSingle(params);

	// For exact output swaps, the amountInMaximum may not have all been spent.
	// If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
	if (amountIn < amountInMaximum) {
	    TransferHelper.safeApprove(DAI, address(swapRouter), 0);
	    TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
	}
}
```

#### スワップコントラクト

スワップコントラクトの完成版は以下になります。

:::details SwapExamples.sol
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract SwapExamples {
    // For the scope of these swap examples,
    // we will detail the design considerations when using
    // `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.

    // It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.
    // More advanced example contracts will detail how to inherit the swap router safely.

    ISwapRouter public immutable swapRouter;

    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }

    /// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9
    /// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
    /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
    /// @param amountIn The exact amount of DAI that will be swapped for WETH9.
    /// @return amountOut The amount of WETH9 received.
    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
        // msg.sender must approve this contract

        // Transfer the specified amount of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Approve the router to spend DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

        // Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
        // We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        // The call to `exactInputSingle` executes the swap.
        amountOut = swapRouter.exactInputSingle(params);
    }

    /// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.
    /// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.
    /// @param amountOut The exact amount of WETH9 to receive from the swap.
    /// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
    /// @return amountIn The amount of DAI actually spent in the swap.
    function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified amount of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);

        // Approve the router to spend the specifed `amountInMaximum` of DAI.
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });

        // Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
        amountIn = swapRouter.exactOutputSingle(params);

        // For exact output swaps, the amountInMaximum may not have all been spent.
        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
        }
    }
}
```
:::

### マルチホップスワップ

以下は**Uniswap**プロトコルのバージョン3（v3）で利用可能な複数のトークン間でスワップを行う方法を示すデモンストレーションコードです。

:::message
この例は学習のためのものであり、実際に使用されているコードではありません。
:::

#### コントラクトのセットアップ

```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';

contract SwapExamples {
	// For the scope of these swap examples,
	// we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.
	// It should be noted that for the sake of these examples we pass in the swap router as a constructor argument instead of inheriting it.
	// More advanced example contracts will detail how to inherit the swap router safely.
	// This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

	ISwapRouter public immutable swapRouter;
    
	address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
	address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
	address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

	// For this example, we will set the pool fee to 0.3%.
	uint24 public constant poolFee = 3000;

	constructor(ISwapRouter _swapRouter) {
		swapRouter = _swapRouter;
	}
	
	...
```

#### 正確なinputマルチホップスワップ

以下のようなプロセスで実行されます。

1. 特定の入力トークンに対して、指定された出力トークン量を得たいと考えます。

2. スワップの進行に従い、中間的なステップとして、異なるトークン間のスワップが行われることがあります。
これらの中間ステップでは、入力トークンから出力トークンへの変換が行われ、その都度手数料（`fee`）がかかります。

3. 最終的に、最大の出力量を得ることを目指して、複数の中間ステップを経て、出力トークンが得られます。

##### パラメーター

- **path（パス）**
	- このパスは(`tokenAddress - fee - tokenAddress`)のような形で、トークン間のスワップシーケンスを示します。
	- 各要素は、トークンのコントラクトアドレスや手数料などを指定し、スワップの途中でどのトークンをどのようにスワップするかを示します。
- **recipient（受取アドレス）**
	- スワップで得た出力アセットを送信するアドレス。
- **deadline（有効期限）**
	- トランザクションが失敗する`unix`時間。
	- これにより、トランザクションが長時間保留されたり、価格が急激に変動する場合にトランザクションが失敗するのを防ぎます。
- **amountIn（入力アセットの量）**
	- 最初に提供される入力アセットの数量。
	- この量をスワップの入力として使用します。
- **amountOutMin（最小出力アセット量）**
	- トランザクションが成功するための条件となる、最小の出力アセット量。
	- この値未満の場合、トランザクションは失敗します。
	- 本番環境では、期待される価格を示すためにSDKを使用したり、高度なセキュリティを持つシステムではオンチェーン価格オラクルを利用することが一般的です。

##### 関数の呼び出し

```solidity
	...
	
	/// @notice swapExactInputMultihop swaps a fixed amount of DAI for a maximum possible amount of WETH9 through an intermediary pool.
	/// For this example, we will swap DAI to USDC, then USDC to WETH9 to achieve our desired output.
	/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
	/// @param amountIn The amount of DAI to be swapped.
	/// @return amountOut The amount of WETH9 received after the swap.
	function swapExactInputMultihop(uint256 amountIn) external returns (uint256 amountOut) {
	// Transfer `amountIn` of DAI to this contract.
	TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

	// Approve the router to spend DAI.
	TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

	// Multiple pool swaps are encoded through bytes called a `path`. A path is a sequence of token addresses and poolFees that define the pools used in the swaps.
	// The format for pool encoding is (tokenIn, fee, tokenOut/tokenIn, fee, tokenOut) where tokenIn/tokenOut parameter is the shared token across the pools.
	// Since we are swapping DAI to USDC and then USDC to WETH9 the path encoding is (DAI, 0.3%, USDC, 0.3%, WETH9).
	ISwapRouter.ExactInputParams memory params =
		ISwapRouter.ExactInputParams({
			path: abi.encodePacked(DAI, poolFee, USDC, poolFee, WETH9),
			recipient: msg.sender,
			deadline: block.timestamp,
			amountIn: amountIn,
			amountOutMinimum: 0
		});

	// Executes the swap.
	amountOut = swapRouter.exactInput(params);
	}
	
	...
```

#### 正確なoutputマルチホップスワップ

任意の入力トークンを固定された出力トークン量と交換する方法です。
このスワップ方法は、マルチホップスワップの中では一般的ではなく、特定の状況で使用されます。コードのほとんどは他のスワップ方法と同じですが、1つ注目すべき違いがあります。
それは、パス（取引の経路）は逆順にエンコードされる点です。
出力トークン量が固定されていて入力トークン量が可変なため、入力トークン量を求めるために逆順にする必要があります。

##### パラメーター

- **path（取引の経路）**
	- パスは、`tokenAddress Fee tokenAddress`の順序でエンコードされますが、実際の実行では逆順に処理されます。
	- これらの変数は、スワップシーケンス内の各プールコントラクトアドレスを計算するために必要です。
	- マルチホップスワップルーターコードは、これらの変数を使用して正しいプールを自動的に見つけ、スワップシーケンス内の各プールで必要なスワップを実行します。

- **recipient（受取アドレス）**
	- 出力アセットの送り先アドレス。
	- スワップによって得たアセットがどこに送信されるかを指定します。
- **deadline（有効期限）**
	- トランザクションが失敗する`unix`時間。
	- これにより、トランザクションが長時間保留されたり、価格が急激に変動する場合にトランザクションが失敗するのを防ぎます。
- **amountOut（出力アセットの希望量）**
	- **WETH9**などの取得したいトークンの量。
	- このスワップは、指定された量の**WETH9**を得るために実行されます。
- **amountInMaximum（最大入力トークン量）**
	- **DAI**を**WETH9**の指定された`amountOut`と交換するために必要となる**DAI**の最大量。
	- このパラメータは、トランザクションが大幅な価格変動によって失敗するのを防ぐために設定されます。

##### 関数呼び出し

```solidity
	...

	/// @notice swapExactOutputMultihop swaps a minimum possible amount of DAI for a fixed amount of WETH through an intermediary pool.
	/// For this example, we want to swap DAI for WETH9 through a USDC pool but we specify the desired amountOut of WETH9. Notice how the path encoding is slightly different in for exact output swaps.
	/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
	/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
	/// @param amountOut The desired amount of WETH9.
	/// @param amountInMaximum The maximum amount of DAI willing to be swapped for the specified amountOut of WETH9.
	/// @return amountIn The amountIn of DAI actually spent to receive the desired amountOut.
	function swapExactOutputMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
	// Transfer the specified `amountInMaximum` to this contract.
	TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
	// Approve the router to spend  `amountInMaximum`.
	TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

	// The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
	// The tokenIn/tokenOut field is the shared token between the two pools used in the multiple pool swap. In this case USDC is the "shared" token.
	// For an exactOutput swap, the first swap that occurs is the swap which returns the eventual desired token.
	// In this case, our desired output token is WETH9 so that swap happens first, and is encoded in the path accordingly.
	ISwapRouter.ExactOutputParams memory params =
		ISwapRouter.ExactOutputParams({
			path: abi.encodePacked(WETH9, poolFee, USDC, poolFee, DAI),
			recipient: msg.sender,
			deadline: block.timestamp,
			amountOut: amountOut,
			amountInMaximum: amountInMaximum
		});

	// Executes the swap, returning the amountIn actually spent.
	amountIn = swapRouter.exactOutput(params);

	// If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
	if (amountIn < amountInMaximum) {
		TransferHelper.safeApprove(DAI, address(swapRouter), 0);
		TransferHelper.safeTransferFrom(DAI, address(this), msg.sender, amountInMaximum - amountIn);
	}
}
```

#### マルチホップコントラクト

:::details SwapExamples.sol
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract SwapExamples {
    // For the scope of these swap examples,
    // we will detail the design considerations when using
    // `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.

    // It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.
    // More advanced example contracts will detail how to inherit the swap router safely.

    ISwapRouter public immutable swapRouter;

    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }

    /// @notice swapInputMultiplePools swaps a fixed amount of DAI for a maximum possible amount of WETH9 through an intermediary pool.
    /// For this example, we will swap DAI to USDC, then USDC to WETH9 to achieve our desired output.
    /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
    /// @param amountIn The amount of DAI to be swapped.
    /// @return amountOut The amount of WETH9 received after the swap.
    function swapExactInputMultihop(uint256 amountIn) external returns (uint256 amountOut) {
        // Transfer `amountIn` of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Approve the router to spend DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

        // Multiple pool swaps are encoded through bytes called a `path`. A path is a sequence of token addresses and poolFees that define the pools used in the swaps.
        // The format for pool encoding is (tokenIn, fee, tokenOut/tokenIn, fee, tokenOut) where tokenIn/tokenOut parameter is the shared token across the pools.
        // Since we are swapping DAI to USDC and then USDC to WETH9 the path encoding is (DAI, 0.3%, USDC, 0.3%, WETH9).
        ISwapRouter.ExactInputParams memory params =
            ISwapRouter.ExactInputParams({
                path: abi.encodePacked(DAI, poolFee, USDC, poolFee, WETH9),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0
            });

        // Executes the swap.
        amountOut = swapRouter.exactInput(params);
    }

    /// @notice swapExactOutputMultihop swaps a minimum possible amount of DAI for a fixed amount of WETH through an intermediary pool.
    /// For this example, we want to swap DAI for WETH9 through a USDC pool but we specify the desired amountOut of WETH9. Notice how the path encoding is slightly different in for exact output swaps.
    /// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.
    /// @param amountOut The desired amount of WETH9.
    /// @param amountInMaximum The maximum amount of DAI willing to be swapped for the specified amountOut of WETH9.
    /// @return amountIn The amountIn of DAI actually spent to receive the desired amountOut.
    function swapExactOutputMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified `amountInMaximum` to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
        // Approve the router to spend  `amountInMaximum`.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        // The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
        // The tokenIn/tokenOut field is the shared token between the two pools used in the multiple pool swap. In this case USDC is the "shared" token.
        // For an exactOutput swap, the first swap that occurs is the swap which returns the eventual desired token.
        // In this case, our desired output token is WETH9 so that swap happpens first, and is encoded in the path accordingly.
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: abi.encodePacked(WETH9, poolFee, USDC, poolFee, DAI),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum
            });

        // Executes the swap, returning the amountIn actually spent.
        amountIn = swapRouter.exactOutput(params);

        // If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransferFrom(DAI, address(this), msg.sender, amountInMaximum - amountIn);
        }
    }
}
```
:::

## 流動性提供

### コントラクトのセットアップ

**Uniswap V3**のカストディアルコントラクトポジションに関するサンプルです。
このカストディアルコントラクトを使用することで、以下の操作が可能になります。

- **ポジションの発行**
	- **Uniswap V3**でトークンの取引を行うためのポジションを作成します。
-  **ポジションへの流動性の追加**
	-  作成したポジションに流動性（トークン）を追加して、取引の準備を整えます。
-  **流動性の減少**
	-  一部またはすべての流動性を撤回して、ポジションからトークンを取り戻します。
-  **手数料の収集**
	-  取引手数料を受け取ります。

```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/base/LiquidityManagement.sol';

contract LiquidityExamples is IERC721Receiver {

	address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
	address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

	uint24 public constant poolFee = 3000;
	
	INonfungiblePositionManager public immutable nonfungiblePositionManager;

	...
```

#### ERC721との相互運用

各NFT（非代替トークン）は、**ERC721**スマートコントラクト内で一意の`tokenId`によって識別されます。
**ERC721**トークンをデポジットするには以下の手順を実行します。

1. **Deposit（デポジット）構造体の作成**
デポジット情報を格納するための「`Deposit`」という構造体を作成します。

2. **NFTとDeposit構造体の紐付け**
次に、各NFTの`tokenId`をキー、`Deposit`構造体を値にしたマッピング配列を作成します。

```solidity
...

struct Deposit {
	address owner;
	uint128 liquidity;
	address token0;
	address token1;
}

mapping(uint256 => Deposit) public deposits;

...
```

#### Constructor

```solidity
...

constructor(
	INonfungiblePositionManager _nonfungiblePositionManager,
	address _factory,
	address _WETH9
) PeripheryImmutableState(_factory, _WETH9) {
	nonfungiblePositionManager = _nonfungiblePositionManager;
}

...
```

#### ERC721トークンの保管を許可

コントラクトが**ERC721**トークンを保管できるようにするには、継承した **IERC721Receiver.sol**コントラクト内の`onERC721Received`関数を実装します。

引数の`from`は使用されないので省略しても問題ないです。

```solidity
...

function onERC721Received(
	address operator,
	address,
	uint256 tokenId,
	bytes calldata
) external override returns (bytes4) {
	// get position information
	_createDeposit(operator, tokenId);
	return this.onERC721Received.selector;
}

...
```

#### デポジットの作成

`deposits`マッピング配列にデータを追加するには、`_createDeposit`という内部関数を作成し、**nonfungiblePositionManager.sol**の`positions`構造体を取得します。
取得した`token0`、`token1、`liquidity`を`deposits`マッピング配列に格納します。

```solidity
function _createDeposit(address owner, uint256 tokenId) internal {
	(, , address token0, address token1, , , , uint128 liquidity, , , , ) = nonfungiblePositionManager.positions(tokenId);

	// set the owner and data for position
	// operator is msg.sender
	deposits[tokenId] = Deposit({owner: owner, liquidity: liquidity, token0: token0, token1: token1});
}
```

#### 流動性提供コントラクト

:::details LiquidityExamples.sol
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '../libraries/TransferHelper.sol';
import '../interfaces/INonfungiblePositionManager.sol';
import '../base/LiquidityManagement.sol';

contract LiquidityExamples is IERC721Receiver {
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    uint24 public constant poolFee = 3000;

    INonfungiblePositionManager public immutable nonfungiblePositionManager;

    /// @notice Represents the deposit of an NFT
    struct Deposit {
        address owner;
        uint128 liquidity;
        address token0;
        address token1;
    }

    /// @dev deposits[tokenId] => Deposit
    mapping(uint256 => Deposit) public deposits;

    constructor(
        INonfungiblePositionManager _nonfungiblePositionManager
    ) {
        nonfungiblePositionManager = _nonfungiblePositionManager;
    }

    // Implementing `onERC721Received` so this contract can receive custody of erc721 tokens
    function onERC721Received(
        address operator,
        address,
        uint256 tokenId,
        bytes calldata
    ) external override returns (bytes4) {
        // get position information

        _createDeposit(operator, tokenId);

        return this.onERC721Received.selector;
    }

    function _createDeposit(address owner, uint256 tokenId) internal {
        (, , address token0, address token1, , , , uint128 liquidity, , , , ) =
            nonfungiblePositionManager.positions(tokenId);

        // set the owner and data for position
        // operator is msg.sender
        deposits[tokenId] = Deposit({owner: owner, liquidity: liquidity, token0: token0, token1: token1});
    }
}
```
:::

### 新しいポジションの作成

#### パラメーター

新しいポジションを作成するには、**nonFungiblePositionManager**の`mint`関数を呼び出す。

この例では、ミントするトークンの量をハードコーディングしています。

:::message
この例は学習のためのものであり、実際に使用されているコードではありません。
:::

```solidity
/// @notice Calls the mint function defined in periphery, mints the same amount of each token. For this example we are providing 1000 DAI and 1000 USDC in liquidity
/// @return tokenId The id of the newly minted ERC721
/// @return liquidity The amount of liquidity for the position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mintNewPosition()
	external
	returns (
		uint256 tokenId,
		uint128 liquidity,
		uint256 amount0,
		uint256 amount1
	)
{
	// For this example, we will provide equal amounts of liquidity in both assets.
	// Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
	uint256 amount0ToMint = 1000;
	uint256 amount1ToMint = 1000;
```

#### ミント呼び出し

**nonfungiblePositionManager**がコントラクトのトークンを使用できるように承認し、`MintParams`構造体を設定し、それを`params`変数に割り当てます。
その後、`mint`関数を呼び出す時にこの`params`を**nonfungiblePositionManager**に渡します。

`TickMath.MIN_TICK`と`TickMath.MAX_TICK`を使用することで、プール全範囲に流動性を提供しています。

:::message
本番環境では、より集中的なポジションを指定できます。
:::

`amount0Min`と`amount1Min`はこの例で`0`に設定されていますが、本番環境では脆弱性の原因となります。
スリッページ保護のない`mint`呼び出しは、価格が正確でない状況で`mint`呼び出しを実行するフロントランニング攻撃に対して脆弱性を持つ可能性があります。

より安全な実装を行うためには、開発者はスリッページの推定プロセスを実装する必要があります。

```solidity
	// Approve the position manager
	TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
	TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);

	INonfungiblePositionManager.MintParams memory params =
		INonfungiblePositionManager.MintParams({
			token0: DAI,
			token1: USDC,
			fee: poolFee,
			tickLower: TickMath.MIN_TICK,
			tickUpper: TickMath.MAX_TICK,
			amount0Desired: amount0ToMint,
			amount1Desired: amount1ToMint,
			amount0Min: 0,
			amount1Min: 0,
			recipient: address(this),
			deadline: block.timestamp
		});

	// Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint
	(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
```

#### Depositマッピングの更新と払い戻し

前章の「**コントラクトのセットアップ**」の内部関数を呼び出すことができます。
その後、ミント処理で残った流動性を`msg.sender`に払い戻します。

```solidity
	// Create a deposit
	_createDeposit(msg.sender, tokenId);

	// Remove allowance and refund in both assets.
	if (amount0 < amount0ToMint) {
		TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
		uint256 refund0 = amount0ToMint - amount0;
		TransferHelper.safeTransfer(DAI, msg.sender, refund0);
	}

	if (amount1 < amount1ToMint) {
		TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
		uint256 refund1 = amount1ToMint - amount1;
		TransferHelper.safeTransfer(USDC, msg.sender, refund1);
	}
}
```

#### コントラクトの例

:::details
```solidity
/// @notice Calls the mint function defined in periphery, mints the same amount of each token. For this example we are providing 1000 DAI and 1000 USDC in liquidity
/// @return tokenId The id of the newly minted ERC721
/// @return liquidity The amount of liquidity for the position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mintNewPosition()
external
returns (
	uint256 tokenId,
	uint128 liquidity,
	uint256 amount0,
	uint256 amount1
)
{
	// For this example, we will provide equal amounts of liquidity in both assets.
	// Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
	uint256 amount0ToMint = 1000;
	uint256 amount1ToMint = 1000;

	// Approve the position manager
	TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
	TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);

	INonfungiblePositionManager.MintParams memory params =
		INonfungiblePositionManager.MintParams({
			token0: DAI,
			token1: USDC,
			fee: poolFee,
			tickLower: TickMath.MIN_TICK,
			tickUpper: TickMath.MAX_TICK,
			amount0Desired: amount0ToMint,
			amount1Desired: amount1ToMint,
			amount0Min: 0,
			amount1Min: 0,
			recipient: address(this),
			deadline: block.timestamp
		});

	// Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint
	(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);

	// Create a deposit
	_createDeposit(msg.sender, tokenId);

	// Remove allowance and refund in both assets.
	if (amount0 < amount0ToMint) {
		TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
		uint256 refund0 = amount0ToMint - amount0;
		TransferHelper.safeTransfer(DAI, msg.sender, refund0);
	}

	if (amount1 < amount1ToMint) {
		TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
		uint256 refund1 = amount1ToMint - amount1;
		TransferHelper.safeTransfer(USDC, msg.sender, refund1);
	}
}
```
:::

### 手数料の徴収

#### 手数料の徴収

流動性の相互作用において、コントラクトが流動性ポジションNFTを保有している必要があります。
したがって、NFTのdepositが関数にコード化されていない例では、コントラクトがすでにNFTを所有していると見なされます。

オーナーポジションの手数料を徴収するには、呼び出し元のアドレスからNFTを転送し、NFTから関連する情報を関数内のローカル変数に代入し、それらの変数を**thenonfungiblePositionManager**に渡して`collect`を呼び出します。

この関数は、ポジションNFTの保管を維持しながら、すべての手数料を回収し、NFTの元の所有者に送ります。

```solidity
/// @notice Collects the fees associated with provided liquidity
/// @dev The contract must hold the erc721 token before it can collect fees
/// @param tokenId The id of the erc721 token
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collectAllFees(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
// Caller must own the ERC721 position
// Call to safeTransfer will trigger `onERC721Received` which must return the selector else transfer will fail
nonfungiblePositionManager.safeTransferFrom(msg.sender, address(this), tokenId);

// set amount0Max and amount1Max to uint256.max to collect all fees
// alternatively can set recipient to msg.sender and avoid another transaction in `sendToOwner`
INonfungiblePositionManager.CollectParams memory params =
	INonfungiblePositionManager.CollectParams({
		tokenId: tokenId,
		recipient: address(this),
		amount0Max: type(uint128).max,
		amount1Max: type(uint128).max
	});

(amount0, amount1) = nonfungiblePositionManager.collect(params);

// send collected feed back to owner
	_sendToOwner(tokenId, amount0, amount1);
}
```

#### 呼び出しアドレスに手数料送付

手数料またはポジション・トークンの形で、あらゆるトークンをNFTの所有者に送ります。

`sendToOwner`では、設定した手数料の額を`safeTransfer`の引数として渡し、手数料をオーナーに送金します。

```solidity
/// @notice Transfers funds to owner of NFT
/// @param tokenId The id of the erc721
/// @param amount0 The amount of token0
/// @param amount1 The amount of token1
function _sendToOwner(
	uint256 tokenId,
	uint256 amount0,
	uint256 amount1
) internal {
	// get owner of contract
	address owner = deposits[tokenId].owner;

	address token0 = deposits[tokenId].token0;
	address token1 = deposits[tokenId].token1;
	// send collected fees to owner
	TransferHelper.safeTransfer(token0, owner, amount0);
	TransferHelper.safeTransfer(token1, owner, amount1);
}
```

### 流動性の低下

ポジションを全て引き出さずにポジションの流動性を減少させます。

この例では、コントラクトがすでにポジションNFTを保有していることを前提とし、呼び出すアドレスはポジションNFTをコントラクトにdepositしたアドレスと同じである必要があります。

:::message
実運用では、`amount0Min`と`amount1Min`を調整して、スリッページを防止する必要があります。
:::

#### 流動性の低下

```solidity
/// @notice A function that decreases the current liquidity by half. An example to show how to call the `decreaseLiquidity` function defined in periphery.
/// @param tokenId The id of the erc721 token
/// @return amount0 The amount received back in token0
/// @return amount1 The amount returned back in token1
function decreaseLiquidityInHalf(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
	// caller must be the owner of the NFT
	require(msg.sender == deposits[tokenId].owner, 'Not the owner');
	// get liquidity data for tokenId
	uint128 liquidity = deposits[tokenId].liquidity;
	uint128 halfLiquidity = liquidity / 2;

	// amount0Min and amount1Min are price slippage checks
	// if the amount received after burning is not greater than these minimums, transaction will fail
	INonfungiblePositionManager.DecreaseLiquidityParams memory params =
		INonfungiblePositionManager.DecreaseLiquidityParams({
			tokenId: tokenId,
			liquidity: halfLiquidity,
			amount0Min: 0,
			amount1Min: 0,
			deadline: block.timestamp
		});

	(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params);

	//send liquidity back to owner
	_sendToOwner(tokenId, amount0, amount1);
}
```

#### 呼び出しアドレスに手数料送付

この関数は、手数料またはポジション・トークンの形で、あらゆるトークンをNFTの所有者に送ります。

`sendToOwner`では、設定した手数料の額を`safeTransfer`の引数として渡し、手数料をオーナーに送金します。

```solidity
/// @notice Transfers funds to owner of NFT
/// @param tokenId The id of the erc721
/// @param amount0 The amount of token0
/// @param amount1 The amount of token1
function _sendToOwner(
	uint256 tokenId,
	uint256 amount0,
	uint256 amount1
) internal {
	// get owner of contract
	address owner = deposits[tokenId].owner;

	address token0 = deposits[tokenId].token0;
	address token1 = deposits[tokenId].token1;
	// send collected fees to owner
	TransferHelper.safeTransfer(token0, owner, amount0);
	TransferHelper.safeTransfer(token1, owner, amount1);
}
```

### 流動性の増加

#### 範囲内の流動性増加

この例では、コントラクトがすでにNFTを保管していることを想定しています。

**Uniswap v3**プロトコルを使用して、指定された流動性ポジションの境界を変更することはできません。

:::message
実運用では、`amount0Min`と`amount1Min`を調整して、スリッページを防止する必要があります。
:::

```solidity
/// @notice Increases liquidity in the current range
/// @dev Pool must be initialized already to add liquidity
/// @param tokenId The id of the erc721 token
/// @param amount0 The amount to add of token0
/// @param amount1 The amount to add of token1
function increaseLiquidityCurrentRange(
	uint256 tokenId,
	uint256 amountAdd0,
	uint256 amountAdd1
)
external
returns (
	uint128 liquidity,
	uint256 amount0,
	uint256 amount1
)
{
	INonfungiblePositionManager.IncreaseLiquidityParams memory params =
		INonfungiblePositionManager.IncreaseLiquidityParams({
			tokenId: tokenId,
			amount0Desired: amountAdd0,
			amount1Desired: amountAdd1,
			amount0Min: 0,
			amount1Min: 0,
			deadline: block.timestamp
		});

	(liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(params);
}
```

### コントラクト全体

**Uniswap V3**のポジションNFTを保管し、手数料の徴収、流動性の増減、新規ポジションの作成によってポジションと流動性を操作できるコントラクトです。

:::details LiquidityExamples.sol
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '../libraries/TransferHelper.sol';
import '../interfaces/INonfungiblePositionManager.sol';
import '../base/LiquidityManagement.sol';

contract LiquidityExamples is IERC721Receiver {
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    uint24 public constant poolFee = 3000;

    INonfungiblePositionManager public immutable nonfungiblePositionManager;

    /// @notice Represents the deposit of an NFT
    struct Deposit {
        address owner;
        uint128 liquidity;
        address token0;
        address token1;
    }

    /// @dev deposits[tokenId] => Deposit
    mapping(uint256 => Deposit) public deposits;

    constructor(
        INonfungiblePositionManager _nonfungiblePositionManager
    ) {
        nonfungiblePositionManager = _nonfungiblePositionManager;
    }

    // Implementing `onERC721Received` so this contract can receive custody of erc721 tokens
    function onERC721Received(
        address operator,
        address,
        uint256 tokenId,
        bytes calldata
    ) external override returns (bytes4) {
        // get position information

        _createDeposit(operator, tokenId);

        return this.onERC721Received.selector;
    }

    function _createDeposit(address owner, uint256 tokenId) internal {
        (, , address token0, address token1, , , , uint128 liquidity, , , , ) =
            nonfungiblePositionManager.positions(tokenId);

        // set the owner and data for position
        // operator is msg.sender
        deposits[tokenId] = Deposit({owner: owner, liquidity: liquidity, token0: token0, token1: token1});
    }

    /// @notice Calls the mint function defined in periphery, mints the same amount of each token.
    /// For this example we are providing 1000 DAI and 1000 USDC in liquidity
    /// @return tokenId The id of the newly minted ERC721
    /// @return liquidity The amount of liquidity for the position
    /// @return amount0 The amount of token0
    /// @return amount1 The amount of token1
    function mintNewPosition()
        external
        returns (
            uint256 tokenId,
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        )
    {
        // For this example, we will provide equal amounts of liquidity in both assets.
        // Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
        uint256 amount0ToMint = 1000;
        uint256 amount1ToMint = 1000;

        // transfer tokens to contract
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amount0ToMint);
        TransferHelper.safeTransferFrom(USDC, msg.sender, address(this), amount1ToMint);

        // Approve the position manager
        TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
        TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);

        INonfungiblePositionManager.MintParams memory params =
            INonfungiblePositionManager.MintParams({
                token0: DAI,
                token1: USDC,
                fee: poolFee,
                tickLower: TickMath.MIN_TICK,
                tickUpper: TickMath.MAX_TICK,
                amount0Desired: amount0ToMint,
                amount1Desired: amount1ToMint,
                amount0Min: 0,
                amount1Min: 0,
                recipient: address(this),
                deadline: block.timestamp
            });

        // Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint
        (tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);

        // Create a deposit
        _createDeposit(msg.sender, tokenId);

        // Remove allowance and refund in both assets.
        if (amount0 < amount0ToMint) {
            TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
            uint256 refund0 = amount0ToMint - amount0;
            TransferHelper.safeTransfer(DAI, msg.sender, refund0);
        }

        if (amount1 < amount1ToMint) {
            TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
            uint256 refund1 = amount1ToMint - amount1;
            TransferHelper.safeTransfer(USDC, msg.sender, refund1);
        }
    }

    /// @notice Collects the fees associated with provided liquidity
    /// @dev The contract must hold the erc721 token before it can collect fees
    /// @param tokenId The id of the erc721 token
    /// @return amount0 The amount of fees collected in token0
    /// @return amount1 The amount of fees collected in token1
    function collectAllFees(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
        // Caller must own the ERC721 position, meaning it must be a deposit

        // set amount0Max and amount1Max to uint256.max to collect all fees
        // alternatively can set recipient to msg.sender and avoid another transaction in `sendToOwner`
        INonfungiblePositionManager.CollectParams memory params =
            INonfungiblePositionManager.CollectParams({
                tokenId: tokenId,
                recipient: address(this),
                amount0Max: type(uint128).max,
                amount1Max: type(uint128).max
            });

        (amount0, amount1) = nonfungiblePositionManager.collect(params);

        // send collected feed back to owner
        _sendToOwner(tokenId, amount0, amount1);
    }

    /// @notice A function that decreases the current liquidity by half. An example to show how to call the `decreaseLiquidity` function defined in periphery.
    /// @param tokenId The id of the erc721 token
    /// @return amount0 The amount received back in token0
    /// @return amount1 The amount returned back in token1
    function decreaseLiquidityInHalf(uint256 tokenId) external returns (uint256 amount0, uint256 amount1) {
        // caller must be the owner of the NFT
        require(msg.sender == deposits[tokenId].owner, 'Not the owner');
        // get liquidity data for tokenId
        uint128 liquidity = deposits[tokenId].liquidity;
        uint128 halfLiquidity = liquidity / 2;

        // amount0Min and amount1Min are price slippage checks
        // if the amount received after burning is not greater than these minimums, transaction will fail
        INonfungiblePositionManager.DecreaseLiquidityParams memory params =
            INonfungiblePositionManager.DecreaseLiquidityParams({
                tokenId: tokenId,
                liquidity: halfLiquidity,
                amount0Min: 0,
                amount1Min: 0,
                deadline: block.timestamp
            });

        (amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params);

        //send liquidity back to owner
        _sendToOwner(tokenId, amount0, amount1);
    }

    /// @notice Increases liquidity in the current range
    /// @dev Pool must be initialized already to add liquidity
    /// @param tokenId The id of the erc721 token
    /// @param amount0 The amount to add of token0
    /// @param amount1 The amount to add of token1
    function increaseLiquidityCurrentRange(
        uint256 tokenId,
        uint256 amountAdd0,
        uint256 amountAdd1
    )
        external
        returns (
            uint128 liquidity,
            uint256 amount0,
            uint256 amount1
        ) {

        TransferHelper.safeTransferFrom(deposits[tokenId].token0, msg.sender, address(this), amountAdd0);
        TransferHelper.safeTransferFrom(deposits[tokenId].token1, msg.sender, address(this), amountAdd1);

        TransferHelper.safeApprove(deposits[tokenId].token0, address(nonfungiblePositionManager), amountAdd0);
        TransferHelper.safeApprove(deposits[tokenId].token1, address(nonfungiblePositionManager), amountAdd1);

        INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager.IncreaseLiquidityParams({
            tokenId: tokenId,
            amount0Desired: amountAdd0,
            amount1Desired: amountAdd1,
            amount0Min: 0,
            amount1Min: 0,
            deadline: block.timestamp
        });

        (liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(params);

    }

    /// @notice Transfers funds to owner of NFT
    /// @param tokenId The id of the erc721
    /// @param amount0 The amount of token0
    /// @param amount1 The amount of token1
    function _sendToOwner(
        uint256 tokenId,
        uint256 amount0,
        uint256 amount1
    ) internal {
        // get owner of contract
        address owner = deposits[tokenId].owner;

        address token0 = deposits[tokenId].token0;
        address token1 = deposits[tokenId].token1;
        // send collected fees to owner
        TransferHelper.safeTransfer(token0, owner, amount0);
        TransferHelper.safeTransfer(token1, owner, amount1);
    }

    /// @notice Transfers the NFT to the owner
    /// @param tokenId The id of the erc721
    function retrieveNFT(uint256 tokenId) external {
        // must be the owner of the NFT
        require(msg.sender == deposits[tokenId].owner, 'Not the owner');
        // transfer ownership to original owner
        nonfungiblePositionManager.safeTransferFrom(address(this), msg.sender, tokenId);
        //remove information related to tokenId
        delete deposits[tokenId];
    }
}
```
:::

## 流動性マイニング

### 概要

#### はじめに

DeFiプロジェクト、トークン作成者、その他の関係者として、**Uniswap V3**プールで範囲を指定しての流動性提供にインセンティブを与えたいと思うかもしれません。
ある特定のインセンティブ付与スキームについて説明していきます。


#### 背景
用語を定義していきます。
ここでは、流動性にインセンティブを与えるプログラムを「**Incentives**」と呼びます。

- **rewardToken（報酬トークン）**
	- 最も重要なパラメータは、インセンティブ提供者は、流動性提供の報酬として配布したい**ERC20**トークンを選択します。
- **pool（プール）**
	- 流動性を提供する**Uniswap V3**プールのアドレス。
- **startTime（開始時間）**
	- 報酬の配布が開始されるUNIXタイムスタンプ。
- **endTime（終了時間）**
	- 報酬の配布が終了するUNIXタイムスタンプ。
- **refundee（返金受取アドレス）**
	- **Incentive**が終了した後に残った報酬を請求する権利を持つアドレス。

各インセンティブには関連する報酬があり、プログラムのライフサイクルの間に分配される報酬トークンの総量が割り当てられます。

#### 報酬の数学

インセンティブがどのようなものか分かったところで、実際に参加者にどのように報酬が配分されるかを見ていきます。

インセンティブ作成者は、報酬額とプログラム期間を選びます。
これは、1秒あたりに配布するリワードトークンの量を選ぶことに直結します。
これを**リワードレート**と呼びます。
つまり、`startTime`と`endTime`の間の1秒ごとに、一定量のトークンが、秒ごとに範囲内にあるある全ての流動性の間で比例配分されます。
重要なのは、これはプログラムへの参加をオプトインした流動性だけでなく、すべての流動性をカウントすることです。
つまり、インセンティブ作成者は、プログラム期間中、（潜在的に）全ての範囲内のLPに分配する価値があると判断した報酬レートを選ぶ必要があります。

#### ステーキング

インセンティブに参加するために、ユーザーは自分のポジションNFTをステーキングコントラクトに預け入れる必要があります。
ステーキングコントラクトはプログラムに参加するNFTから流動性が削除されないことを保証する必要があるためです。

いったんdepositされると、ユーザーのNFTが結び付けられている**Uniswap V3**プールのアクティブなインセンティブに任意の数だけステークすることができます（これは最初の預託でアトミックに発生する可能性があることに注意してください）。ステークされたNFTは、上記のアルゴリズムに従って直ちに報酬を獲得し始めます。ユーザーは、プログラムが進行している間、獲得したリワードトークンを定期的に請求することも、オーバーヘッドを最小限に抑えるためにプログラムが終了するまで請求するのを待つこともできます。

ユーザーが**Incentive（報酬プログラム）** に参加する最初のステップは、自分のポジションNFTを特定のステーキングコントラクトアドレスに預けることです。
このアクションにより、一時的にNFTの管理権限がこのコントラクトに移ります。
これは、ステーキングコントラクトがNFTをプログラムに参加している間は流動性（取引可能な状態）が削除されないことを保証する必要があるためです。

一度NFTを預けたら、ユーザーはそのNFTを**Uniswap V3**プールに関連付けられた任意のアクティブな**Incentive**にステーキング（投資）することができます（初回の預け入れと同時にこれが行われることもあることに注意してください）。
ステーキングされたNFTは、上記で説明したアルゴリズムに従って、すぐに報酬を獲得し始めます。
ユーザーはプログラムが進行中の間に定期的に蓄積された報酬トークンを取り出すことができます。
また、オーバーヘッド（手数料や手間）を最小限に抑えるために、プログラムが終了するまで報酬を請求しない選択肢もあります。

#### プログラムの結論

**プログラムの終了条件*

1. `block.timestamp >= endTime`（ブロックのタイムスタンプが終了時刻を超えた場合）
プログラムの期間が終了している必要があります。
ただし、これはプログラムの正式な終了を意味しません。
なぜなら、一部のユーザーは期限切れの直前やその後も報酬を最大限に得るために参加しているかもしれないからです。
また、これは次の条件に直結します。

2. すべてのNFTがステーク解除されること
プログラムは、それに参加したすべてのNFTがステーク解除されたときにのみ終了できます。
これを常に可能にするために、プログラムが終了した後、誰でもNFTをステーク解除できます（ただし、当然ながらNFTの所有者以外の未払いの報酬トークンを請求することはできません）。
これにより、すべてのユーザーが自分自身をステーク解除しなくても、誰かが手動でステーク解除できるようになり、プログラムを終了できます。

**未割り当ての報酬が残る場合**

未割り当ての報酬が残る条件は、報酬率がすべての範囲内の流動性に対して同じであることを考えると理解しやすいです。
ただし、報酬トークンを請求できるのはプログラムの参加者のみであるため、すべてのプログラムにおいて請求できない報酬トークンが残る可能性があります。
したがって、**refundees**（返金申請者）はプログラムを終了させるインセンティブを持ちます。
この複雑な設計は、**Uniswap V3**の流動性に応じて報酬を一貫して割り当てる難しさに起因しています。

最後に、終了時刻を超えてプログラムに残っているステーカーは、報酬がわずかに増減する可能性があります。
これらの変化の大きさは、ステーカーが総アクティブ流動性のシェア、終了時刻後のステーク時間、およびステーク解除の順番に依存します。
2倍の期間では報酬の半分が残り、3倍の場合は1/3が残るように、報酬は期間に比例して減少することもあります。

:::message
- プログラム期間が1週間（7日間）で、報酬が週ごとに支払われるとします。
- ユーザーAはプログラム終了時刻を迎えてもステークを解除しないでいる場合、次の週には報酬を受け取れず、ステーキング状態が続きます。
- プログラム終了から2週間後、ユーザーAはようやくステークを解除し、報酬を請求します。

この場合、ユーザーAの報酬は、終了時刻から2週間経過したため、報酬が減少している可能性があります。
報酬は時間とともに減少し、ユーザーAが遅く報酬を請求したため、最終的な報酬が減少しています。

この変化の大きさは、以下の要因に依存します。

- ステーカーの総アクティブ流動性に占めるシェア
	- ユーザーAが総ステークの何％を持っているか。
- 終了時刻後のステーク時間
	- ユーザーAがステークを解除するまでの時間。
- ステーク解除の順序
	- 他のユーザーがステークを解除する順序と競合する要素。

最悪の場合、報酬は経過時間に比例して減少します。

- 2倍の期間経過後、報酬は元の半分になり、ユーザーAは元の報酬の半分しか受け取れないかもしれません。
- 3倍の期間経過後、報酬は元の1/3になり、ユーザーAは元の報酬の1/3しか受け取れないかもしれません。
:::

しかし、ゲーム理論の観点からは、ステーカーは報酬を最大化するためにできるだけ早くステークを解除し、報酬を請求することを試みるはずです。
なぜなら、**refundees**（返金申請者）も残りの報酬を取り戻すことに関心があり、ステークを解除しないユーザーが多い場合、一斉にステークを解除しようとする可能性が高いからです。

## フラッシュスワップ

### はじめに

このスマートコントラクトは、V3プールで「**フラッシュ**」と呼ばれる操作を実行し、トークン0とトークン1を全額引き出します。
そして、同じトークンペアである別の手数料ティア（手数料率）を持つプールで、取り出したトークン0とトークン1を交換します。
交換が完了したら、最初のプールに元のトークンを返し、利益を元の呼び出し元アドレスに送金します。

#### フラッシュトランザクションの概要

フラッシュトランザクションは、トークンの送金条件が満たされる前にトークンの残高を移動させる方法です。
スワップの文脈では、これは入力が受け取られる前に出力がスワップから送信されることを意味します。

:::message
通常のトークン送信では、送信元アカウントが一定の条件を満たす必要があります（例：十分な残高があること、トランザクションのガスが支払われていることなど）。
しかし、フラッシュトランザクションでは、これらの条件を待たずにトークンを送信できます。
つまり、トークンの送信条件が通常よりも柔軟で、トランザクションが即座に実行されることを可能にします。

スワップの場合、入力（トークンの提供）が受け取られる前に、出力（スワップの結果として得られるトークン）が送信されることがあります。
このアプローチは、トランザクションのオーバーヘッドを最小限に抑え、効率的なトークン交換を実現します。
:::

**Uniswap V3**では、Poolコントラクト内で新しい関数である「`flash`」が導入されています。
`flash`は、指定された量のトークン0とトークン1を指定した受け取りアドレスに引き出します。
引き出した量には、スワップ手数料も含まれます。
トランザクションの終了時には、引き出した量とスワップ手数料がプールに支払われることになります。
また、`flash`には「`data`」という4つ目のパラメータが含まれており、このパラメータを使用して、後で必要なデータを関数に渡し、後でデコードすることができます。

```solidity
function flash(
	address recipient,
	uint256 amount0,
	uint256 amount1,
	bytes calldata data
) external override lock noDelegateCall {
```

#### フラッシュコールバック

トークンが`flash`によって引き出された後、それらのトークンがどのように返済されるかを理解するには、`flash`関数のコードを見る必要があります。
`flash`関数の一部に以下のようなコードがあります。

```solidity
IUniswapV3FlashCallback(msg.sender).uniswapV3FlashCallback(fee0, fee1, data);
```

この部分の処理では、`msg.sender`によって`FlashCallback`関数が呼び出されます。
これにより、プールに支払う残高を計算するために必要な手数料データが渡され、`data`パラメータにエンコードされた任意のデータも渡されます。

**Uniswap V3**では、カスタムロジックでオーバーライド可能な以下の3つの異なるコールバック関数があります。

- `uniswapV3SwapCallback`
- `uniswapV3MintCallback`
- `uniswapV3FlashCallback`

アービトラージコントラクトを書くためには、`flash`を呼び出し、**uniswapV3FlashCallback**をオーバーライドして、トランザクションの実行を完了するために必要なステップを実行します。

#### V3コントラクトの継承

**IUniswapV3FlashCallback**と**PeripheryPayments**を継承してそれぞれの機能を利用します。
注意点としては、これらの継承元のスマートコントラクトも多くの他のスマートコントラクトを拡張していることがあり、例として**LowGasSafeMath**を利用して`uint256`および`int256`の型を拡張しています。

```solidity
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol';
import '@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol';

import '@uniswap/v3-periphery/contracts/base/PeripheryPayments.sol';
import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
import '@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol';
import '@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract PairFlash is IUniswapV3FlashCallback, PeripheryPayments {
	using LowGasSafeMath for uint256;
	using LowGasSafeMath for int256;

	ISwapRouter public immutable swapRouter;

	constructor(
		ISwapRouter _swapRouter,
		address _factory,
		address _WETH9
	) PeripheryImmutableState(_factory, _WETH9) {
		swapRouter = _swapRouter;
	}
```

### フラッシュ呼び出し

#### 構造体のパラメーター

`flash`を呼び出すには、最初の呼び出しに必要な`flash`パラメータと、コールバック時に渡したいパラメータが必要になります。

`FlashParams`構造体には、プールから引き出したいトークンのアドレスと量、どのプールから引き出し、どのプールとスワップするかを決定するための3つのデータが含まれます。

```solidity
struct FlashParams {
	address token0;
	address token1;
	uint24 fee1;
	uint256 amount0;
	uint256 amount1;
	uint24 fee2;
	uint24 fee3;
}
```

`FlashCallbackData`構造体には、コールバック時に送信するデータが格納されます。
これは、**PoolAddress**ライブラリによって返された、手数料ティアが一致するソートされたトークンを表す`poolKey`が含まれます。

```solidity
struct FlashCallbackData {
	uint256 amount0;
	uint256 amount1;
	address payer;
	PoolAddress.PoolKey poolKey;
	uint24 poolFee2;
	uint24 poolFee3;
}
```

#### プールキー

`Flashparams`（`params`としてメモリ上に宣言してある）から関連するパラメータを`poolKey`に代入します。

```solidity
function initFlash(FlashParams memory params) external {
	PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee1});
}
```

次に、`pool`を[`IUniswapV3Pool`]型として宣言し、目的のプールのコントラクトで`flash`を呼び出せるようにします。

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

#### フラッシュ呼び出し

最後に、宣言したプールで`flash`を呼び出します。
最後のパラメーターに対して、`FlashCallbackDataをabi.encode`を実行します。
この`FlashCallbackData`はコールバックでデコードされ、トランザクションの次のステップを通知するために使用されます。

```solidity
pool.flash(
	address(this),
	params.amount0,
	params.amount1,
	abi.encode(
		FlashCallbackData({
			amount0: params.amount0,
			amount1: params.amount1,
			payer: msg.sender,
			poolKey: poolKey,
			poolFee2: params.fee2,
			poolFee3: params.fee3
		})
	)
);
```

関数全体のコードは以下になります。

```solidity
//fee1 is the fee of the pool from the initial borrow
//fee2 is the fee of the first pool to arb from
//fee3 is the fee of the second pool to arb from
struct FlashParams {
	address token0;
	address token1;
	uint24 fee1;
	uint256 amount0;
	uint256 amount1;
	uint24 fee2;
	uint24 fee3;
}

// fee2 and fee3 are the two other fees associated with the two other pools of token0 and token1
struct FlashCallbackData {
	uint256 amount0;
	uint256 amount1;
	address payer;
	PoolAddress.PoolKey poolKey;
	uint24 poolFee2;
	uint24 poolFee3;
}

function initFlash(FlashParams memory params) external {
	PoolAddress.PoolKey memory poolKey = PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee1});
	IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
	pool.flash(
		address(this),
		params.amount0,
		params.amount1,
		abi.encode(
			FlashCallbackData({
				amount0: params.amount0,
				amount1: params.amount1,
				payer: msg.sender,
				poolKey: poolKey,
				poolFee2: params.fee2,
				poolFee3: params.fee3
			})
		)
	);
}
```

### フラッシュコールバック

フラッシュ・コールバックをカスタム・ロジックでオーバーライドして、必要なスワップを実行し、元の`msg.sender`に利益を支払います。

`uniswapV3FlashCallback`関数を宣言し、オーバーライドします。

```solidity
function uniswapV3FlashCallback(
	uint256 fee0,
	uint256 fee1,
	bytes calldata data
) external override {
	FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData));
```

各コールバックは、呼び出し元が本物の**V3**プールであることを検証する必要があります。
そうでない場合、プールコントラクトは、コールバック関数を操作するEOAを介した攻撃を受けやすくなります。

```solidity
CallbackValidation.verifyCallback(factory, decoded.poolKey);
```

アドレス型のローカル変数をtoken0とtoken1に割り当てて、ルーターがフラッシュからトークンとやり取りできるようにします。

```solidity
address token0 = decoded.poolKey.token0;
address token1 = decoded.poolKey.token1;

TransferHelper.safeApprove(token0, address(swapRouter), decoded.amount0);
TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1);
```

2つのスワップトランザクションで得られる最小のトークン量を計算します。
これらのトランザクションは、利益がない場合には実行しないようにするために、最低限の条件を設定しています。

- `amount1Min`
	- トークン1の最小必要量。
- `amount0Min`
	- トークン0の最小必要量。

これらの値は、計算されたトークンの数量に手数料を追加しています。
手数料は`fee0`と`fee1`として表され、それぞれトークン0とトークン1の手数料を示します。
トークンの最小量を手数料を考慮に入れて計算することで、利益がないトランザクションが実行されないようになります。

```solidity
uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1);
uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0);
```

#### スワップの開始

```solidity
uint256 amountOut0 =
	swapRouter.exactInputSingle(
	ISwapRouter.ExactInputSingleParams({
		tokenIn: token1,
		tokenOut: token0,
		fee: decoded.poolFee2,
		recipient: address(this),
		deadline: block.timestamp + 200,
		amountIn: decoded.amount1,
		amountOutMinimum: amount0Min,
		sqrtPriceLimitX96: 0
	})
);
```

上記のコードは最初のスワップトランザクションを呼び出す部分です。
この呼び出しでは、ルーターインターフェースコントラクトの`exactInputSingle`関数を使用しています。
この呼び出しでは、`amount0In`を最小の取引トークン量（最小アウトプット）として使用し、スワップの戻り値を`amountOut0`に格納しています。

以下の2つの新しい機能が導入されています。

- `sqrtPriceLimitX96`
	- スワップがプールの価格を変更できる範囲を制限します。
	- 価格は常にトークン0をトークン1との比率で表す形式でプールコントラクトに格納されます。
	- この引数は、ユーザーが特定の価格までスワップを行いたい場合に便利です。
	- この例では、この引数を無効にするために`0`に設定します。

- `deadline`
	- トランザクションがリバート（無効になる）するタイムスタンプです。
	- これにより、トランザクションが長時間保留された場合に価格環境が急激に変動することからトランザクションを保護します。
	- この例では、シンプルさのためにだいぶ先の将来のタイムスタンプに設定します。

このスワップトランザクションでは、最初に元のプールから引き出した`amount1`を、最大可能なアウトプット量に交換するための固定の入力として使用します。

:::message
元々持っていたトークン1を、最大限のトークン0に交換しようとしています。
:::

この関数呼び出しは、前回のトークンペアで決定されたプールに対して実行されます。
ただし、手数料ティアと呼ばれるトレードプールの種類があり、このスワップでは手数料ティアのリストで次にある手数料ティアで実行されます。

:::message
手数料ティアは手数料率が異なるトレードプールのことで、低い手数料ティアから高い手数料ティアまで複数の種類があります。

前回のトークンペアに基づいて、現在の手数料ティア（例：0.05%手数料、0.3%手数料、1%手数料など）が決定され、その手数料ティアのプールでトークンを交換することになります。手数料ティアごとに手数料率が異なり、トレードのコストや収益に影響します。
したがって、このスワップは現在の手数料ティアに従ってトークンを交換し、手数料の違いを考慮しています。
:::

```solidity
uint256 amountOut1 = swapRouter.exactInputSingle(
	ISwapRouter.ExactInputSingleParams({
		tokenIn: token0,
		tokenOut: token1,
		fee: decoded.poolFee3,
		recipient: address(this),
		deadline: block.timestamp + 200,
		amountIn: decoded.amount0,
		amountOutMinimum: amount1Min,
		sqrtPriceLimitX96: 0
	})
);
```

上記のコードは、2つ目のスワップトランザクションを設定する部分です。
このスワップでは、最後の手数料ティア（**fee tier**）を使用し、元のプールから引き出した`amount0`をトークンの取引に使います。

:::message
このスワップは以下のステップを実行します。

1. **最後の手数料ティア（fee tier）を選択します**
前回のスワップでは、異なる手数料率のプールでトークンを交換しましたが、今回は最後の手数料ティアを使用します。
これにより、最後の手数料率のプールでトークンを交換することになります。

2. **元のプールから引き出したamount0を使用**
以前に引き出したトークン0（`amount0`）を、このスワップトランザクションの入力として利用します。
つまり、元のプールから得たトークン0を、このスワップでトークン1に交換しようとしています。

このように、2つ目のスワップトランザクションは最後の手数料ティアのプールで行われ、元のプールから引き出したトークン0を交換対象としています。
これにより、最適なトークン交換を行い、利益を確保しようとしています。
:::

#### プールからの返済

元のプールに対して`flash`トランザクションの返済を行うために、まずプールに支払う残高を計算し、ルーターがコントラクト内のトークンをプールに戻すために承認を行います。
以下のコードは、このプロセスを実行する部分です。

```solidity
uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0);
uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1);

TransferHelper.safeApprove(token0, address(this), amount0Owed);
TransferHelper.safeApprove(token1, address(this), amount1Owed);
```

:::message
1. **`amount0Owed`および`amount1Owed`の計算**
元のプールに支払うべきトークンの残高を計算しています。
これには、以前のスワップトランザクションで計算した`amount0`および`amount1`に、それぞれの手数料（`fee0`と`fee1`）を加えます。つ

2. **承認の設定**
次に、計算した残高を元のプールに戻すために、ルーターに対してコントラクト内のトークンを転送する権限を与えるための承認を設定します。
これは、`TransferHelper.safeApprove`を使用して行われます。
トークン0およびトークン1に対して、それぞれの支払い残高（`amount0Owed`と`amount1Owed`）をコントラクト自体に承認することで、ルーターがこれらのトークンをプールに戻すことができるようになります。

このプロセスにより、元のプールへの支払い残高が計算され、ルーターがトークンを正確に返済できるようになります。
:::

トークンに支払う残高がある場合、シンプルなロジックを使用して`pay`関数を呼び出します。
この時に注意すべきなのは、コールバック関数は`internal`としてマークされていますが、プール自体によって呼び出されているため`pay`関数を呼び出すことができるということです。

```solidity
if (amount0Owed > 0) pay(token0, address(this), msg.sender, amount0Owed);
if (amount1Owed > 0) pay(token1, address(this), msg.sender, amount1Owed);
```

:::message
1. **`amount0Owed`および`amount1Owed`の確認**
まず、トークン0およびトークン1に支払うべき残高（`amount0Owed`と`amount1Owed`）が`0`でないか確認します。
もし残高が`0`でなければ、支払う必要があることを示します。

2. **`pay`関数の呼び出し**
残高が`0`でない場合、`pay`関数を呼び出します。
この関数はトークンの支払いを行うために使用されます。
トークン0またはトークン1（`token0`と`token1`）に対して、残高（`amount0Owed`と`amount1Owed`）を指定し、支払いを行います。
この呼び出しにより、トークンに対する支払いが実行されます。

このコードは、トークンに支払うべき残高がある場合に、`pay`関数を使用して支払いを行うシンプルなロジックを示しています。
:::

利益を支払うために、`initFlash`関数を実行し、`flash`トランザクションをトリガーし、その後コールバックの呼び出し元である`msg.sender`に対して利益を支払います。

```solidity
if (amountOut0 > amount0Owed) {
	uint256 profit0 = LowGasSafeMath.sub(amountOut0, amount0Owed);

	TransferHelper.safeApprove(token0, address(this), profit0);
	pay(token0, address(this), decoded.payer, profit0);
}

if (amountOut1 > amount1Owed) {
	uint256 profit1 = LowGasSafeMath.sub(amountOut1, amount1Owed);
	TransferHelper.safeApprove(token0, address(this), profit1);
	pay(token1, address(this), decoded.payer, profit1);
}
```

:::message
1. **`amountOut0`および`amountOut1`の確認**
まず、トークン0とトークン1の利益（`amountOut0`と`amountOut1`）が、それぞれトークン0とトークン1に支払うべき残高（`amount0Owed`と`amount1Owed`）よりも大きいか確認します。
これにより利益が発生しているかどうかを確認できます。

2. **利益の計算**
利益が発生している場合、利益を計算します。
具体的には、`amountOut0`から`amount0Owed`を引いて、トークン0の利益（`profit0`）を計算し、`amountOut1`から`amount1Owed`を引いて、トークン1の利益（`profit1`）を計算します。

3. **承認と支払い**
利益が発生している場合、トークン0とトークン1に対して利益を承認（`safeApprove`）し、それから`pay`関数を使用して元の`initFlash`関数の呼び出し元である`decoded.payer`に対して利益を支払います。
:::


#### 関数全体のコード

:::details uniswapV3FlashCallback
```solidity
function uniswapV3FlashCallback(
	uint256 fee0,
	uint256 fee1,
	bytes calldata data
) external override {
	FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData));
	CallbackValidation.verifyCallback(factory, decoded.poolKey);

	address token0 = decoded.poolKey.token0;
	address token1 = decoded.poolKey.token1;

	TransferHelper.safeApprove(token0, address(swapRouter), decoded.amount0);
	TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1);

	// profitable check
	// exactInputSingle will fail if this amount not met
	uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1);
	uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0);

	// call exactInputSingle for swapping token1 for token0 in pool w/fee2
	uint256 amountOut0 =
	swapRouter.exactInputSingle(
		ISwapRouter.ExactInputSingleParams({
			tokenIn: token1,
			tokenOut: token0,
			fee: decoded.poolFee2,
			recipient: address(this),
			deadline: block.timestamp + 200,
			amountIn: decoded.amount1,
			amountOutMinimum: amount0Min,
			sqrtPriceLimitX96: 0
		})
	);

	// call exactInputSingle for swapping token0 for token 1 in pool w/fee3
	uint256 amountOut1 =
	swapRouter.exactInputSingle(
		ISwapRouter.ExactInputSingleParams({
			tokenIn: token0,
			tokenOut: token1,
			fee: decoded.poolFee3,
			recipient: address(this),
			deadline: block.timestamp + 200,
			amountIn: decoded.amount0,
			amountOutMinimum: amount1Min,
			sqrtPriceLimitX96: 0
		})
	);

	// end up with amountOut0 of token0 from first swap and amountOut1 of token1 from second swap
	uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0);
	uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1);

	TransferHelper.safeApprove(token0, address(this), amount0Owed);
	TransferHelper.safeApprove(token1, address(this), amount1Owed);

	if (amount0Owed > 0) pay(token0, address(this), msg.sender, amount0Owed);
	if (amount1Owed > 0) pay(token1, address(this), msg.sender, amount1Owed);

	// if profitable pay profits to payer
	if (amountOut0 > amount0Owed) {
		uint256 profit0 = LowGasSafeMath.sub(amountOut0, amount0Owed);

		TransferHelper.safeApprove(token0, address(this), profit0);
		pay(token0, address(this), decoded.payer, profit0);
	}
	if (amountOut1 > amount1Owed) {
		uint256 profit1 = LowGasSafeMath.sub(amountOut1, amount1Owed);
		TransferHelper.safeApprove(token0, address(this), profit1);
		pay(token1, address(this), decoded.payer, profit1);
	}
}
```
:::

### コントラクト全体

:::details PairFlash.sol
```solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3FlashCallback.sol';
import '@uniswap/v3-core/contracts/libraries/LowGasSafeMath.sol';

import '@uniswap/v3-periphery/contracts/base/PeripheryPayments.sol';
import '@uniswap/v3-periphery/contracts/base/PeripheryImmutableState.sol';
import '@uniswap/v3-periphery/contracts/libraries/PoolAddress.sol';
import '@uniswap/v3-periphery/contracts/libraries/CallbackValidation.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

/// @title Flash contract implementation
/// @notice An example contract using the Uniswap V3 flash function
contract PairFlash is IUniswapV3FlashCallback, PeripheryImmutableState, PeripheryPayments {
    using LowGasSafeMath for uint256;
    using LowGasSafeMath for int256;

    ISwapRouter public immutable swapRouter;

    constructor(
        ISwapRouter _swapRouter,
        address _factory,
        address _WETH9
    ) PeripheryImmutableState(_factory, _WETH9) {
        swapRouter = _swapRouter;
    }

    /// @param fee0 The fee from calling flash for token0
    /// @param fee1 The fee from calling flash for token1
    /// @param data The data needed in the callback passed as FlashCallbackData from `initFlash`
    /// @notice implements the callback called from flash
    /// @dev fails if the flash is not profitable, meaning the amountOut from the flash is less than the amount borrowed
    function uniswapV3FlashCallback(
        uint256 fee0,
        uint256 fee1,
        bytes calldata data
    ) external override {
        FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData));
        CallbackValidation.verifyCallback(factory, decoded.poolKey);

        address token0 = decoded.poolKey.token0;
        address token1 = decoded.poolKey.token1;

        TransferHelper.safeApprove(token0, address(swapRouter), decoded.amount0);
        TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1);

        // profitable check
        // exactInputSingle will fail if this amount not met
        uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1);
        uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0);

        // call exactInputSingle for swapping token1 for token0 in pool w/fee2
        uint256 amountOut0 =
            swapRouter.exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: token1,
                    tokenOut: token0,
                    fee: decoded.poolFee2,
                    recipient: address(this),
                    deadline: block.timestamp,
                    amountIn: decoded.amount1,
                    amountOutMinimum: amount0Min,
                    sqrtPriceLimitX96: 0
                })
            );

        // call exactInputSingle for swapping token0 for token 1 in pool w/fee3
        uint256 amountOut1 =
            swapRouter.exactInputSingle(
                ISwapRouter.ExactInputSingleParams({
                    tokenIn: token0,
                    tokenOut: token1,
                    fee: decoded.poolFee3,
                    recipient: address(this),
                    deadline: block.timestamp,
                    amountIn: decoded.amount0,
                    amountOutMinimum: amount1Min,
                    sqrtPriceLimitX96: 0
                })
            );

        // end up with amountOut0 of token0 from first swap and amountOut1 of token1 from second swap
        uint256 amount0Owed = LowGasSafeMath.add(decoded.amount0, fee0);
        uint256 amount1Owed = LowGasSafeMath.add(decoded.amount1, fee1);

        TransferHelper.safeApprove(token0, address(this), amount0Owed);
        TransferHelper.safeApprove(token1, address(this), amount1Owed);

        if (amount0Owed > 0) pay(token0, address(this), msg.sender, amount0Owed);
        if (amount1Owed > 0) pay(token1, address(this), msg.sender, amount1Owed);

        // if profitable pay profits to payer
        if (amountOut0 > amount0Owed) {
            uint256 profit0 = LowGasSafeMath.sub(amountOut0, amount0Owed);

            TransferHelper.safeApprove(token0, address(this), profit0);
            pay(token0, address(this), decoded.payer, profit0);
        }
        if (amountOut1 > amount1Owed) {
            uint256 profit1 = LowGasSafeMath.sub(amountOut1, amount1Owed);
            TransferHelper.safeApprove(token0, address(this), profit1);
            pay(token1, address(this), decoded.payer, profit1);
        }
    }

    //fee1 is the fee of the pool from the initial borrow
    //fee2 is the fee of the first pool to arb from
    //fee3 is the fee of the second pool to arb from
    struct FlashParams {
        address token0;
        address token1;
        uint24 fee1;
        uint256 amount0;
        uint256 amount1;
        uint24 fee2;
        uint24 fee3;
    }
    // fee2 and fee3 are the two other fees associated with the two other pools of token0 and token1
    struct FlashCallbackData {
        uint256 amount0;
        uint256 amount1;
        address payer;
        PoolAddress.PoolKey poolKey;
        uint24 poolFee2;
        uint24 poolFee3;
    }

    /// @param params The parameters necessary for flash and the callback, passed in as FlashParams
    /// @notice Calls the pools flash function with data needed in `uniswapV3FlashCallback`
    function initFlash(FlashParams memory params) external {
        PoolAddress.PoolKey memory poolKey =
            PoolAddress.PoolKey({token0: params.token0, token1: params.token1, fee: params.fee1});
        IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
        // recipient of borrowed amounts
        // amount of token0 requested to borrow
        // amount of token1 requested to borrow
        // need amount 0 and amount1 in callback to pay back pool
        // recipient of flash should be THIS contract
        pool.flash(
            address(this),
            params.amount0,
            params.amount1,
            abi.encode(
                FlashCallbackData({
                    amount0: params.amount0,
                    amount1: params.amount1,
                    payer: msg.sender,
                    poolKey: poolKey,
                    poolFee2: params.fee2,
                    poolFee3: params.fee3
                })
            )
        );
    }
}
```
:::

# 最後に

今回の記事では、以下の**Uniswap V3**について公式ドキュメントを翻訳・補足しながらまとめてきました。

https://docs.uniswap.org/contracts/v3/overview

いかがだったでしょうか？

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

https://chaldene.net/

https://qiita.com/cardene

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58
