# [Bunzz Decipher] UniswapV3Factoryコントラクトを理解しよう！

> UniswapV3Factoryコントラクトは、Uniswap V3プロトコルの中で重要な機能です。このコントラクトは、ユーザーが流動性プールを作成し、それに参加するためのプラットフォームを提供しています。

- URL: https://cardene.dev/articles/61ee19994743fd
- 公開日: 2023-08-25
- 更新日: 2023-08-25
- トピック: Blockchain
- タグ: solidity, ethereum, smartcontract, defi

# はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

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

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

https://www.bunzz.dev/decipher

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

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

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

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

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

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

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

# 概要

**UniswapV3Factory**コントラクトは、**Uniswap V3**プロトコルの中で重要な機能です。
このコントラクトは、ユーザーが流動性プールを作成し、それに参加するためのプラットフォームを提供しています。

このコントラクトの主な目的は、ユーザーが異なるトークン同士の取引を行ったり、流動性（資金）を提供したりできるようにすることです。
例えば、ユーザーがトークンAとトークンBの取引を行いたい場合、このコントラクトを通じてそれらのトークンの流動性プールを作成し、取引に参加することができます。

**UniswapV3Factory**コントラクトの主な役割は以下になります。

### プールの展開
新しい流動性プールを作成する際、このコントラクトは必要な設定やパラメータを適切に初期化してプールを展開します。
例えば、取引手数料の設定やトークンのペアなどがこれに該当します。

### プールの管理
既存のプールの設定や状態を管理します。
ユーザーが手数料の変更やプール情報の取得を行えるようにする役割を果たします。

### プールとのやり取り
ユーザーがプールとやり取りできるように、取引や流動性の提供、取り出しを容易にします。
これにより、ユーザーは取引を行い、流動性を提供することができます。

### セキュリティ
ユーザーの資金を守るため、不正行為を防ぐためのセキュリティ対策を実装しています。
認証されたユーザー以外がプールの設定変更などを行えないようにアクセス制御が行われます。
また、数学的な計算に関するセキュリティも考慮されています。

:::message
**UniswapV3Factory**コントラクト内では、数学的な計算に関するセキュリティが非常に重要な役割を果たしています。
特に、価格計算やトークンの交換に関連する計算は正確で安全性が確保されている必要があります。

以下は、**UniswapV3Factory**コントラクトで行われる数学的な計算に関するセキュリティの考慮事項の一部です。

**浮動小数点数の扱い**
浮動小数点数は計算中に丸めエラーや精度の問題を引き起こす可能性があります。
コントラクト内では、固定小数点数や整数演算を使用するなどして、計算の正確性と安全性を確保する工夫がされています。

**オーバーフローとアンダーフローの防止**
大きな数値の計算中にオーバーフローやアンダーフローが発生する可能性があります。
適切な数値範囲内で計算を行うための制御が行われています。

**安全な数学ライブラリの使用**
コントラクト内では、安全な数学ライブラリや関数を使用することが一般的です。
これにより、脆弱性やエラーが最小限に抑えられ、計算結果が正確で信頼性のあるものとなります。

**テストと検証**
数学的な計算に関するコードは十分なテストと検証が行われています。
ユニットテストやプラットフォーム上での実際の動作テストによって、計算が正確で予測可能なものであることが確認されます。

**オープンな監査**
プロトコルやコントラクトのセキュリティは、外部の専門家による監査を受けることで確認されることがあります。
これにより、セキュリティ上の懸念が特定され、修正されることが保証されます。

**UniswapV3Factory**コントラクト内の数学的な計算は、正確性とセキュリティを確保するために慎重に設計され、テストおよび検証されています。
:::

# 使い方

## 概要
**UniswapV3Factory**コントラクトは、**Uniswap V3**プールの作成と管理を行うスマートコントラクトです。
このコントラクトを使用することで、新しいプールの作成や既存のプールに関する情報の取得などの様々な操作を行うことができます。

## コントラクトの目標
**UniswapV3Factory**コントラクトの目標は、**Uniswap V3**プールの作成と管理のための分散型プラットフォームを提供することです。
ユーザーはトークンのペアや手数料ティアなどのパラメータをカスタマイズして新しいプールを作成し、作成されたプールと対話するための関数を利用することができます。

## 目標達成の手順
**Uniswap V3**プールの作成と管理の目標を達成するためには、次の手順が必要です。

1. **UniswapV3Factory**コントラクトを展開する。
2. `createPool`関数を呼び出して、指定したパラメータで新しいプールを作成する。
3. **UniswapV3Factory**コントラクトが提供するさまざまな関数を使用して、作成されたプールとやり取りを行う。

このようにして、**UniswapV3Factory**コントラクトを使用してユーザーは新しいプールの作成や管理を行うことができ、**Uniswap V3**プロトコルの恩恵を受けることができます。

## 使い方

### 新しいプールの作成
新しいプールを作成するには、以下の手順に従ってください。

1. **UniswapV3Factory**コントラクトを展開します。
2. `createPool`関数を以下のパラメータで呼び出します。
- `tokenA`
   - トークンペアの最初のトークンのアドレス。
- `tokenB`
   - トークンペアの2番目のトークンのアドレス。
- `fee`
   - プールの手数料ティア。
- `sqrtPriceX96`
   - トークンペアの初期平方根価格。
`createPool`関数は新しい**UniswapV3Pool**コントラクトをデプロイし、そのアドレスを返します。

### プール情報の取得
既存のプールに関する情報を取得するには、以下の関数を使用します。

- `getPool`
	- 特定のトークンペアと手数料ティアに対する**UniswapV3Pool**コントラクトのアドレスを返します。
- `allPools`
	- ファクトリによって作成されたすべてのプールアドレスの配列を返します。

### プールとのやり取り
プールが作成されると、**UniswapV3Pool**コントラクトが提供する関数を使用してプールと対話できます。
主な関数には次のものがあります。

- `swap`
	- プール内でトークンをスワップします。
- `mint`
	- プール内で新しい流動性を追加します。
- `burn`
	- プールから流動性を削減します。
- `collect`
	- プールから手数料を収集します。

このようにして、**UniswapV3Factory**コントラクトを使用して新しいプールを作成し、既存のプールに関する情報を取得し、プールとやり取りすることができます。

## 関連EIP/ERC

**UniswapV3Factory**コントラクトは以下のEIP/ERCに関連しています。

- [ERC-1620](https://eips.ethereum.org/EIPS/eip-1620): Money Streaming Standard
- [ERC-165](https://eips.ethereum.org/EIPS/eip-165): Standard Interface Detection

https://qiita.com/cardene/items/541184977a5cba51b759

- [ERC-173](https://eips.ethereum.org/EIPS/eip-173): Ownership Standard

https://qiita.com/cardene/items/0b8cdc156404920a8cbd

- [ERC-721](https://eips.ethereum.org/EIPS/eip-721): Non-Fungible Token Standard

https://chaldene.net/erc721

- [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155): Multi-Token Standard

https://chaldene.net/erc1155

- [ERC-20](https://eips.ethereum.org/EIPS/eip-20): Token Standard

https://chaldene.net/erc20

- [ERC-721](https://eips.ethereum.org/EIPS/eip-721): Non-Fungible Token Standard

https://chaldene.net/erc721

**UniswapV3Factory**コントラクトとそれに関連するコントラクト、ライブラリの詳細については、それぞれのソースコードファイルとドキュメントを参照してください。

# パラメータ

特になし。

# コントラクト

## UniswapV3Factory

:::details UniswapV3Factory
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

import './interfaces/IUniswapV3Factory.sol';

import './UniswapV3PoolDeployer.sol';
import './NoDelegateCall.sol';

import './UniswapV3Pool.sol';

/// @title Canonical Uniswap V3 factory
/// @notice Deploys Uniswap V3 pools and manages ownership and control over pool protocol fees
contract UniswapV3Factory is IUniswapV3Factory, UniswapV3PoolDeployer, NoDelegateCall {
    /// @inheritdoc IUniswapV3Factory
    address public override owner;

    /// @inheritdoc IUniswapV3Factory
    mapping(uint24 => int24) public override feeAmountTickSpacing;
    /// @inheritdoc IUniswapV3Factory
    mapping(address => mapping(address => mapping(uint24 => address))) public override getPool;

    constructor() {
        owner = msg.sender;
        emit OwnerChanged(address(0), msg.sender);

        feeAmountTickSpacing[500] = 10;
        emit FeeAmountEnabled(500, 10);
        feeAmountTickSpacing[3000] = 60;
        emit FeeAmountEnabled(3000, 60);
        feeAmountTickSpacing[10000] = 200;
        emit FeeAmountEnabled(10000, 200);
    }

    /// @inheritdoc IUniswapV3Factory
    function createPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) external override noDelegateCall returns (address pool) {
        require(tokenA != tokenB);
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0));
        int24 tickSpacing = feeAmountTickSpacing[fee];
        require(tickSpacing != 0);
        require(getPool[token0][token1][fee] == address(0));
        pool = deploy(address(this), token0, token1, fee, tickSpacing);
        getPool[token0][token1][fee] = pool;
        // populate mapping in the reverse direction, deliberate choice to avoid the cost of comparing addresses
        getPool[token1][token0][fee] = pool;
        emit PoolCreated(token0, token1, fee, tickSpacing, pool);
    }

    /// @inheritdoc IUniswapV3Factory
    function setOwner(address _owner) external override {
        require(msg.sender == owner);
        emit OwnerChanged(owner, _owner);
        owner = _owner;
    }

    /// @inheritdoc IUniswapV3Factory
    function enableFeeAmount(uint24 fee, int24 tickSpacing) public override {
        require(msg.sender == owner);
        require(fee < 1000000);
        // tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that
        // TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick
        // 16384 ticks represents a >5x price change with ticks of 1 bips
        require(tickSpacing > 0 && tickSpacing < 16384);
        require(feeAmountTickSpacing[fee] == 0);

        feeAmountTickSpacing[fee] = tickSpacing;
        emit FeeAmountEnabled(fee, tickSpacing);
    }
}
```
:::

### owner

**概要**
**UniswapV3Factory**コントラクトのオーナーアドレスを保持する変数。

**詳細**
オーナーは新しいプールの作成や設定変更などを制御する権限を持ちます。

---

### feeAmountTickSpacing

**概要**
手数料ティアに対するtickスペーシング（価格間隔）を保持するマッピング配列。

**詳細**
このマッピングは、各手数料ティアに対して設定されたtickスペーシング（価格間隔）を格納します。tickスペーシングは、価格の変動を制御し、トレードの精度を調整します。

**引数**
- `fee`
	- 手数料ティア（手数料率）。

**戻り値**
- `int24`
	- 指定された手数料ティアに対するtickスペーシング。

---

### getPool

**概要**
トークンペアと手数料ティアに対する**UniswapV3Pool**コントラクトのアドレスを保持する3次元マッピング配列。

**詳細**
この3次元マッピングは、特定のトークンペアと手数料ティアに対して、対応する**UniswapV3Pool**コントラクトのアドレスを格納します。
これにより、特定のトークンの組み合わせと手数料ティアに対するプールのアドレスを簡単に取得できます。

**引数**
- `tokenA`
	- トークンペアの1つ目のトークンのアドレス。
- `tokenB`
	- トークンペアの2つ目のトークンのアドレス。
- `fee`
	- 手数料ティア。

**戻り値**
- `address`
	- 指定されたトークンペアと手数料ティアに対する**UniswapV3Pool**コントラクトのアドレス。

---

### createPool

**概要**
新しいプールを作成する関数。

**引数**
- `tokenA`
	- トークンペアの1つ目のトークンのアドレス。
- `tokenB`
	- トークンペアの2つ目のトークンのアドレス。
- `fee`
	- 手数料ティア。

**戻り値**
- `address`
	- 作成された**Uniswap V3**プールのアドレス。

---

### setOwner

**概要**
オーナーアドレスを設定する関数。

**詳細**
コントラクトのオーナーを設定します。
オーナーは特定の操作を行う権限を持ちます。
ただし、オーナー自身のみがオーナーを変更できます。

**引数**
- `_owner`
	- 新しいオーナーアドレス。

**戻り値**
なし

---

### enableFeeAmount

**概要**
手数料ティアに対するtickスペーシングを設定する関数。

**詳細**
手数料ティアに対してtickスペーシング（価格間隔）を設定します。
オーナーのみがこの関数を使用でき、設定されたtickスペーシングによって価格変動の制御が行われます。

**引数**
- `fee`
	- 手数料ティア。
- `tickSpacing`
	- tickスペーシング（価格間隔）。

**戻り値**
なし

---

## UniswapV3PoolDeployer

:::details UniswapV3PoolDeployer

```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

import './interfaces/IUniswapV3PoolDeployer.sol';

import './UniswapV3Pool.sol';

contract UniswapV3PoolDeployer is IUniswapV3PoolDeployer {
    struct Parameters {
        address factory;
        address token0;
        address token1;
        uint24 fee;
        int24 tickSpacing;
    }

    /// @inheritdoc IUniswapV3PoolDeployer
    Parameters public override parameters;

    /// @dev Deploys a pool with the given parameters by transiently setting the parameters storage slot and then
    /// clearing it after deploying the pool.
    /// @param factory The contract address of the Uniswap V3 factory
    /// @param token0 The first token of the pool by address sort order
    /// @param token1 The second token of the pool by address sort order
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @param tickSpacing The spacing between usable ticks
    function deploy(
        address factory,
        address token0,
        address token1,
        uint24 fee,
        int24 tickSpacing
    ) internal returns (address pool) {
        parameters = Parameters({factory: factory, token0: token0, token1: token1, fee: fee, tickSpacing: tickSpacing});
        pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());
        delete parameters;
    }
}
```
:::

### Parameters

**概要**
プールのデプロイに関するパラメータを保持する構造体。

**パラメータ**
- `factory`
	- **Uniswap V3**ファクトリのコントラクトアドレス。
- `token0`
	- トークンペアの1つ目のトークンのアドレス。
- `token1`
	- トークンペアの2つ目のトークンのアドレス。
- `fee`
	- 手数料ティア。
- `tickSpacing`
	- 価格間隔（tickスペーシング）。

**戻り値**
なし

---

### parameters

**概要**
プールのデプロイに関するパラメータを保持する変数。

**詳細**
`Parameters`という構造体のインスタンスを格納するため、プールのデプロイに関する情報が含まれます。


**戻り値**
- `Parameters`
	- プールのデプロイに関するパラメータが含まれる構造体のインスタンス。

---

### deploy

**概要**
プールをデプロイするための関数。

**詳細**
指定されたパラメータを使用して**Uniswap V3**プールをデプロイします。
デプロイ時には、パラメータを**storage slot**に一時的に設定し、プールのデプロイ後に削除します。

**引数**
- `factory`
	- **Uniswap V3**ファクトリのコントラクトアドレス。
- `token0`
	- トークンペアの1つ目のトークンのアドレス。
- `token1`
	- トークンペアの2つ目のトークンのアドレス。
- `fee`
	- 手数料ティア。
- `tickSpacing`
	- 価格間隔（tickスペーシング）。


**戻り値**
- `address`
	- デプロイされた**Uniswap V3**プールのアドレス。

---

## NoDelegateCall

**概要**
デリゲートコール（`delegatecall`）を子コントラクトのメソッドに対して防止するためのコントラクト。

**詳細**
この抽象コントラクトは、`delegatecall`を使用して別のコントラクトのメソッドを呼び出すことを防ぐための修飾子を提供します。
`delegatecall`は、他のコントラクトのコードを呼び出すメカニズムであり、この基本コントラクトを使用することで、意図しないコントラクト間の干渉を防ぐことができます。

:::details NoDelegateCall

```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

/// @title Prevents delegatecall to a contract
/// @notice Base contract that provides a modifier for preventing delegatecall to methods in a child contract
abstract contract NoDelegateCall {
    /// @dev The original address of this contract
    address private immutable original;

    constructor() {
        // Immutables are computed in the init code of the contract, and then inlined into the deployed bytecode.
        // In other words, this variable won't change when it's checked at runtime.
        original = address(this);
    }

    /// @dev Private method is used instead of inlining into modifier because modifiers are copied into each method,
    ///     and the use of immutable means the address bytes are copied in every place the modifier is used.
    function checkNotDelegateCall() private view {
        require(address(this) == original);
    }

    /// @notice Prevents delegatecall into the modified method
    modifier noDelegateCall() {
        checkNotDelegateCall();
        _;
    }
}
```
:::

### original

**概要**
このコントラクトの元のアドレスを保持する変数。

**詳細**
この変数は、コントラクトがデプロイされた際の元のアドレスを格納します。
イミュータブル（不変）変数であり、デプロイ後に変更されることはありません。
`delegatecall`の実行元のアドレスと比較することで、`delegatecall`を防止します。


**戻り値**
- `address`
	- このコントラクトの元のアドレス。

---

### checkNotDelegateCall

**概要**
デリゲートコールでないことを確認するためのプライベート関数。

**詳細**
デリゲートコールが行われていないことを確認します。
デリゲートコールを行っている場合、この関数の`require`文によってエラーが発生します。
デリゲートコールが検出されると、意図せぬコントラクト間の呼び出しを防ぐことができます。

**戻り値**
なし

---

### noDelegateCall

**概要**
修飾されたメソッドに対するデリゲートコールを防止するための修飾子。

**詳細**
修飾されたメソッドがデリゲートコールによって呼び出されないことを確認します。
デリゲートコールを防ぐことで、コントラクト間の意図しない相互作用を防止します。

## UniswapV3Pool

:::details UniswapV3Pool
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity =0.7.6;

import './interfaces/IUniswapV3Pool.sol';

import './NoDelegateCall.sol';

import './libraries/LowGasSafeMath.sol';
import './libraries/SafeCast.sol';
import './libraries/Tick.sol';
import './libraries/TickBitmap.sol';
import './libraries/Position.sol';
import './libraries/Oracle.sol';

import './libraries/FullMath.sol';
import './libraries/FixedPoint128.sol';
import './libraries/TransferHelper.sol';
import './libraries/TickMath.sol';
import './libraries/LiquidityMath.sol';
import './libraries/SqrtPriceMath.sol';
import './libraries/SwapMath.sol';

import './interfaces/IUniswapV3PoolDeployer.sol';
import './interfaces/IUniswapV3Factory.sol';
import './interfaces/IERC20Minimal.sol';
import './interfaces/callback/IUniswapV3MintCallback.sol';
import './interfaces/callback/IUniswapV3SwapCallback.sol';
import './interfaces/callback/IUniswapV3FlashCallback.sol';

contract UniswapV3Pool is IUniswapV3Pool, NoDelegateCall {
    using LowGasSafeMath for uint256;
    using LowGasSafeMath for int256;
    using SafeCast for uint256;
    using SafeCast for int256;
    using Tick for mapping(int24 => Tick.Info);
    using TickBitmap for mapping(int16 => uint256);
    using Position for mapping(bytes32 => Position.Info);
    using Position for Position.Info;
    using Oracle for Oracle.Observation[65535];

    /// @inheritdoc IUniswapV3PoolImmutables
    address public immutable override factory;
    /// @inheritdoc IUniswapV3PoolImmutables
    address public immutable override token0;
    /// @inheritdoc IUniswapV3PoolImmutables
    address public immutable override token1;
    /// @inheritdoc IUniswapV3PoolImmutables
    uint24 public immutable override fee;

    /// @inheritdoc IUniswapV3PoolImmutables
    int24 public immutable override tickSpacing;

    /// @inheritdoc IUniswapV3PoolImmutables
    uint128 public immutable override maxLiquidityPerTick;

    struct Slot0 {
        // the current price
        uint160 sqrtPriceX96;
        // the current tick
        int24 tick;
        // the most-recently updated index of the observations array
        uint16 observationIndex;
        // the current maximum number of observations that are being stored
        uint16 observationCardinality;
        // the next maximum number of observations to store, triggered in observations.write
        uint16 observationCardinalityNext;
        // the current protocol fee as a percentage of the swap fee taken on withdrawal
        // represented as an integer denominator (1/x)%
        uint8 feeProtocol;
        // whether the pool is locked
        bool unlocked;
    }
    /// @inheritdoc IUniswapV3PoolState
    Slot0 public override slot0;

    /// @inheritdoc IUniswapV3PoolState
    uint256 public override feeGrowthGlobal0X128;
    /// @inheritdoc IUniswapV3PoolState
    uint256 public override feeGrowthGlobal1X128;

    // accumulated protocol fees in token0/token1 units
    struct ProtocolFees {
        uint128 token0;
        uint128 token1;
    }
    /// @inheritdoc IUniswapV3PoolState
    ProtocolFees public override protocolFees;

    /// @inheritdoc IUniswapV3PoolState
    uint128 public override liquidity;

    /// @inheritdoc IUniswapV3PoolState
    mapping(int24 => Tick.Info) public override ticks;
    /// @inheritdoc IUniswapV3PoolState
    mapping(int16 => uint256) public override tickBitmap;
    /// @inheritdoc IUniswapV3PoolState
    mapping(bytes32 => Position.Info) public override positions;
    /// @inheritdoc IUniswapV3PoolState
    Oracle.Observation[65535] public override observations;

    /// @dev Mutually exclusive reentrancy protection into the pool to/from a method. This method also prevents entrance
    /// to a function before the pool is initialized. The reentrancy guard is required throughout the contract because
    /// we use balance checks to determine the payment status of interactions such as mint, swap and flash.
    modifier lock() {
        require(slot0.unlocked, 'LOK');
        slot0.unlocked = false;
        _;
        slot0.unlocked = true;
    }

    /// @dev Prevents calling a function from anyone except the address returned by IUniswapV3Factory#owner()
    modifier onlyFactoryOwner() {
        require(msg.sender == IUniswapV3Factory(factory).owner());
        _;
    }

    constructor() {
        int24 _tickSpacing;
        (factory, token0, token1, fee, _tickSpacing) = IUniswapV3PoolDeployer(msg.sender).parameters();
        tickSpacing = _tickSpacing;

        maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(_tickSpacing);
    }

    /// @dev Common checks for valid tick inputs.
    function checkTicks(int24 tickLower, int24 tickUpper) private pure {
        require(tickLower < tickUpper, 'TLU');
        require(tickLower >= TickMath.MIN_TICK, 'TLM');
        require(tickUpper <= TickMath.MAX_TICK, 'TUM');
    }

    /// @dev Returns the block timestamp truncated to 32 bits, i.e. mod 2**32. This method is overridden in tests.
    function _blockTimestamp() internal view virtual returns (uint32) {
        return uint32(block.timestamp); // truncation is desired
    }

    /// @dev Get the pool's balance of token0
    /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
    /// check
    function balance0() private view returns (uint256) {
        (bool success, bytes memory data) =
            token0.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }

    /// @dev Get the pool's balance of token1
    /// @dev This function is gas optimized to avoid a redundant extcodesize check in addition to the returndatasize
    /// check
    function balance1() private view returns (uint256) {
        (bool success, bytes memory data) =
            token1.staticcall(abi.encodeWithSelector(IERC20Minimal.balanceOf.selector, address(this)));
        require(success && data.length >= 32);
        return abi.decode(data, (uint256));
    }

    /// @inheritdoc IUniswapV3PoolDerivedState
    function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
        external
        view
        override
        noDelegateCall
        returns (
            int56 tickCumulativeInside,
            uint160 secondsPerLiquidityInsideX128,
            uint32 secondsInside
        )
    {
        checkTicks(tickLower, tickUpper);

        int56 tickCumulativeLower;
        int56 tickCumulativeUpper;
        uint160 secondsPerLiquidityOutsideLowerX128;
        uint160 secondsPerLiquidityOutsideUpperX128;
        uint32 secondsOutsideLower;
        uint32 secondsOutsideUpper;

        {
            Tick.Info storage lower = ticks[tickLower];
            Tick.Info storage upper = ticks[tickUpper];
            bool initializedLower;
            (tickCumulativeLower, secondsPerLiquidityOutsideLowerX128, secondsOutsideLower, initializedLower) = (
                lower.tickCumulativeOutside,
                lower.secondsPerLiquidityOutsideX128,
                lower.secondsOutside,
                lower.initialized
            );
            require(initializedLower);

            bool initializedUpper;
            (tickCumulativeUpper, secondsPerLiquidityOutsideUpperX128, secondsOutsideUpper, initializedUpper) = (
                upper.tickCumulativeOutside,
                upper.secondsPerLiquidityOutsideX128,
                upper.secondsOutside,
                upper.initialized
            );
            require(initializedUpper);
        }

        Slot0 memory _slot0 = slot0;

        if (_slot0.tick < tickLower) {
            return (
                tickCumulativeLower - tickCumulativeUpper,
                secondsPerLiquidityOutsideLowerX128 - secondsPerLiquidityOutsideUpperX128,
                secondsOutsideLower - secondsOutsideUpper
            );
        } else if (_slot0.tick < tickUpper) {
            uint32 time = _blockTimestamp();
            (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
                observations.observeSingle(
                    time,
                    0,
                    _slot0.tick,
                    _slot0.observationIndex,
                    liquidity,
                    _slot0.observationCardinality
                );
            return (
                tickCumulative - tickCumulativeLower - tickCumulativeUpper,
                secondsPerLiquidityCumulativeX128 -
                    secondsPerLiquidityOutsideLowerX128 -
                    secondsPerLiquidityOutsideUpperX128,
                time - secondsOutsideLower - secondsOutsideUpper
            );
        } else {
            return (
                tickCumulativeUpper - tickCumulativeLower,
                secondsPerLiquidityOutsideUpperX128 - secondsPerLiquidityOutsideLowerX128,
                secondsOutsideUpper - secondsOutsideLower
            );
        }
    }

    /// @inheritdoc IUniswapV3PoolDerivedState
    function observe(uint32[] calldata secondsAgos)
        external
        view
        override
        noDelegateCall
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s)
    {
        return
            observations.observe(
                _blockTimestamp(),
                secondsAgos,
                slot0.tick,
                slot0.observationIndex,
                liquidity,
                slot0.observationCardinality
            );
    }

    /// @inheritdoc IUniswapV3PoolActions
    function increaseObservationCardinalityNext(uint16 observationCardinalityNext)
        external
        override
        lock
        noDelegateCall
    {
        uint16 observationCardinalityNextOld = slot0.observationCardinalityNext; // for the event
        uint16 observationCardinalityNextNew =
            observations.grow(observationCardinalityNextOld, observationCardinalityNext);
        slot0.observationCardinalityNext = observationCardinalityNextNew;
        if (observationCardinalityNextOld != observationCardinalityNextNew)
            emit IncreaseObservationCardinalityNext(observationCardinalityNextOld, observationCardinalityNextNew);
    }

    /// @inheritdoc IUniswapV3PoolActions
    /// @dev not locked because it initializes unlocked
    function initialize(uint160 sqrtPriceX96) external override {
        require(slot0.sqrtPriceX96 == 0, 'AI');

        int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);

        (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp());

        slot0 = Slot0({
            sqrtPriceX96: sqrtPriceX96,
            tick: tick,
            observationIndex: 0,
            observationCardinality: cardinality,
            observationCardinalityNext: cardinalityNext,
            feeProtocol: 0,
            unlocked: true
        });

        emit Initialize(sqrtPriceX96, tick);
    }

    struct ModifyPositionParams {
        // the address that owns the position
        address owner;
        // the lower and upper tick of the position
        int24 tickLower;
        int24 tickUpper;
        // any change in liquidity
        int128 liquidityDelta;
    }

    /// @dev Effect some changes to a position
    /// @param params the position details and the change to the position's liquidity to effect
    /// @return position a storage pointer referencing the position with the given owner and tick range
    /// @return amount0 the amount of token0 owed to the pool, negative if the pool should pay the recipient
    /// @return amount1 the amount of token1 owed to the pool, negative if the pool should pay the recipient
    function _modifyPosition(ModifyPositionParams memory params)
        private
        noDelegateCall
        returns (
            Position.Info storage position,
            int256 amount0,
            int256 amount1
        )
    {
        checkTicks(params.tickLower, params.tickUpper);

        Slot0 memory _slot0 = slot0; // SLOAD for gas optimization

        position = _updatePosition(
            params.owner,
            params.tickLower,
            params.tickUpper,
            params.liquidityDelta,
            _slot0.tick
        );

        if (params.liquidityDelta != 0) {
            if (_slot0.tick < params.tickLower) {
                // current tick is below the passed range; liquidity can only become in range by crossing from left to
                // right, when we'll need _more_ token0 (it's becoming more valuable) so user must provide it
                amount0 = SqrtPriceMath.getAmount0Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            } else if (_slot0.tick < params.tickUpper) {
                // current tick is inside the passed range
                uint128 liquidityBefore = liquidity; // SLOAD for gas optimization

                // write an oracle entry
                (slot0.observationIndex, slot0.observationCardinality) = observations.write(
                    _slot0.observationIndex,
                    _blockTimestamp(),
                    _slot0.tick,
                    liquidityBefore,
                    _slot0.observationCardinality,
                    _slot0.observationCardinalityNext
                );

                amount0 = SqrtPriceMath.getAmount0Delta(
                    _slot0.sqrtPriceX96,
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    _slot0.sqrtPriceX96,
                    params.liquidityDelta
                );

                liquidity = LiquidityMath.addDelta(liquidityBefore, params.liquidityDelta);
            } else {
                // current tick is above the passed range; liquidity can only become in range by crossing from right to
                // left, when we'll need _more_ token1 (it's becoming more valuable) so user must provide it
                amount1 = SqrtPriceMath.getAmount1Delta(
                    TickMath.getSqrtRatioAtTick(params.tickLower),
                    TickMath.getSqrtRatioAtTick(params.tickUpper),
                    params.liquidityDelta
                );
            }
        }
    }

    /// @dev Gets and updates a position with the given liquidity delta
    /// @param owner the owner of the position
    /// @param tickLower the lower tick of the position's tick range
    /// @param tickUpper the upper tick of the position's tick range
    /// @param tick the current tick, passed to avoid sloads
    function _updatePosition(
        address owner,
        int24 tickLower,
        int24 tickUpper,
        int128 liquidityDelta,
        int24 tick
    ) private returns (Position.Info storage position) {
        position = positions.get(owner, tickLower, tickUpper);

        uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization
        uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization

        // if we need to update the ticks, do it
        bool flippedLower;
        bool flippedUpper;
        if (liquidityDelta != 0) {
            uint32 time = _blockTimestamp();
            (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
                observations.observeSingle(
                    time,
                    0,
                    slot0.tick,
                    slot0.observationIndex,
                    liquidity,
                    slot0.observationCardinality
                );

            flippedLower = ticks.update(
                tickLower,
                tick,
                liquidityDelta,
                _feeGrowthGlobal0X128,
                _feeGrowthGlobal1X128,
                secondsPerLiquidityCumulativeX128,
                tickCumulative,
                time,
                false,
                maxLiquidityPerTick
            );
            flippedUpper = ticks.update(
                tickUpper,
                tick,
                liquidityDelta,
                _feeGrowthGlobal0X128,
                _feeGrowthGlobal1X128,
                secondsPerLiquidityCumulativeX128,
                tickCumulative,
                time,
                true,
                maxLiquidityPerTick
            );

            if (flippedLower) {
                tickBitmap.flipTick(tickLower, tickSpacing);
            }
            if (flippedUpper) {
                tickBitmap.flipTick(tickUpper, tickSpacing);
            }
        }

        (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
            ticks.getFeeGrowthInside(tickLower, tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128);

        position.update(liquidityDelta, feeGrowthInside0X128, feeGrowthInside1X128);

        // clear any tick data that is no longer needed
        if (liquidityDelta < 0) {
            if (flippedLower) {
                ticks.clear(tickLower);
            }
            if (flippedUpper) {
                ticks.clear(tickUpper);
            }
        }
    }

    /// @inheritdoc IUniswapV3PoolActions
    /// @dev noDelegateCall is applied indirectly via _modifyPosition
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external override lock returns (uint256 amount0, uint256 amount1) {
        require(amount > 0);
        (, int256 amount0Int, int256 amount1Int) =
            _modifyPosition(
                ModifyPositionParams({
                    owner: recipient,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: int256(amount).toInt128()
                })
            );

        amount0 = uint256(amount0Int);
        amount1 = uint256(amount1Int);

        uint256 balance0Before;
        uint256 balance1Before;
        if (amount0 > 0) balance0Before = balance0();
        if (amount1 > 0) balance1Before = balance1();
        IUniswapV3MintCallback(msg.sender).uniswapV3MintCallback(amount0, amount1, data);
        if (amount0 > 0) require(balance0Before.add(amount0) <= balance0(), 'M0');
        if (amount1 > 0) require(balance1Before.add(amount1) <= balance1(), 'M1');

        emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
    }

    /// @inheritdoc IUniswapV3PoolActions
    function collect(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external override lock returns (uint128 amount0, uint128 amount1) {
        // we don't need to checkTicks here, because invalid positions will never have non-zero tokensOwed{0,1}
        Position.Info storage position = positions.get(msg.sender, tickLower, tickUpper);

        amount0 = amount0Requested > position.tokensOwed0 ? position.tokensOwed0 : amount0Requested;
        amount1 = amount1Requested > position.tokensOwed1 ? position.tokensOwed1 : amount1Requested;

        if (amount0 > 0) {
            position.tokensOwed0 -= amount0;
            TransferHelper.safeTransfer(token0, recipient, amount0);
        }
        if (amount1 > 0) {
            position.tokensOwed1 -= amount1;
            TransferHelper.safeTransfer(token1, recipient, amount1);
        }

        emit Collect(msg.sender, recipient, tickLower, tickUpper, amount0, amount1);
    }

    /// @inheritdoc IUniswapV3PoolActions
    /// @dev noDelegateCall is applied indirectly via _modifyPosition
    function burn(
        int24 tickLower,
        int24 tickUpper,
        uint128 amount
    ) external override lock returns (uint256 amount0, uint256 amount1) {
        (Position.Info storage position, int256 amount0Int, int256 amount1Int) =
            _modifyPosition(
                ModifyPositionParams({
                    owner: msg.sender,
                    tickLower: tickLower,
                    tickUpper: tickUpper,
                    liquidityDelta: -int256(amount).toInt128()
                })
            );

        amount0 = uint256(-amount0Int);
        amount1 = uint256(-amount1Int);

        if (amount0 > 0 || amount1 > 0) {
            (position.tokensOwed0, position.tokensOwed1) = (
                position.tokensOwed0 + uint128(amount0),
                position.tokensOwed1 + uint128(amount1)
            );
        }

        emit Burn(msg.sender, tickLower, tickUpper, amount, amount0, amount1);
    }

    struct SwapCache {
        // the protocol fee for the input token
        uint8 feeProtocol;
        // liquidity at the beginning of the swap
        uint128 liquidityStart;
        // the timestamp of the current block
        uint32 blockTimestamp;
        // the current value of the tick accumulator, computed only if we cross an initialized tick
        int56 tickCumulative;
        // the current value of seconds per liquidity accumulator, computed only if we cross an initialized tick
        uint160 secondsPerLiquidityCumulativeX128;
        // whether we've computed and cached the above two accumulators
        bool computedLatestObservation;
    }

    // the top level state of the swap, the results of which are recorded in storage at the end
    struct SwapState {
        // the amount remaining to be swapped in/out of the input/output asset
        int256 amountSpecifiedRemaining;
        // the amount already swapped out/in of the output/input asset
        int256 amountCalculated;
        // current sqrt(price)
        uint160 sqrtPriceX96;
        // the tick associated with the current price
        int24 tick;
        // the global fee growth of the input token
        uint256 feeGrowthGlobalX128;
        // amount of input token paid as protocol fee
        uint128 protocolFee;
        // the current liquidity in range
        uint128 liquidity;
    }

    struct StepComputations {
        // the price at the beginning of the step
        uint160 sqrtPriceStartX96;
        // the next tick to swap to from the current tick in the swap direction
        int24 tickNext;
        // whether tickNext is initialized or not
        bool initialized;
        // sqrt(price) for the next tick (1/0)
        uint160 sqrtPriceNextX96;
        // how much is being swapped in in this step
        uint256 amountIn;
        // how much is being swapped out
        uint256 amountOut;
        // how much fee is being paid in
        uint256 feeAmount;
    }

    /// @inheritdoc IUniswapV3PoolActions
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external override noDelegateCall returns (int256 amount0, int256 amount1) {
        require(amountSpecified != 0, 'AS');

        Slot0 memory slot0Start = slot0;

        require(slot0Start.unlocked, 'LOK');
        require(
            zeroForOne
                ? sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > TickMath.MIN_SQRT_RATIO
                : sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < TickMath.MAX_SQRT_RATIO,
            'SPL'
        );

        slot0.unlocked = false;

        SwapCache memory cache =
            SwapCache({
                liquidityStart: liquidity,
                blockTimestamp: _blockTimestamp(),
                feeProtocol: zeroForOne ? (slot0Start.feeProtocol % 16) : (slot0Start.feeProtocol >> 4),
                secondsPerLiquidityCumulativeX128: 0,
                tickCumulative: 0,
                computedLatestObservation: false
            });

        bool exactInput = amountSpecified > 0;

        SwapState memory state =
            SwapState({
                amountSpecifiedRemaining: amountSpecified,
                amountCalculated: 0,
                sqrtPriceX96: slot0Start.sqrtPriceX96,
                tick: slot0Start.tick,
                feeGrowthGlobalX128: zeroForOne ? feeGrowthGlobal0X128 : feeGrowthGlobal1X128,
                protocolFee: 0,
                liquidity: cache.liquidityStart
            });

        // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit
        while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) {
            StepComputations memory step;

            step.sqrtPriceStartX96 = state.sqrtPriceX96;

            (step.tickNext, step.initialized) = tickBitmap.nextInitializedTickWithinOneWord(
                state.tick,
                tickSpacing,
                zeroForOne
            );

            // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds
            if (step.tickNext < TickMath.MIN_TICK) {
                step.tickNext = TickMath.MIN_TICK;
            } else if (step.tickNext > TickMath.MAX_TICK) {
                step.tickNext = TickMath.MAX_TICK;
            }

            // get the price for the next tick
            step.sqrtPriceNextX96 = TickMath.getSqrtRatioAtTick(step.tickNext);

            // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted
            (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep(
                state.sqrtPriceX96,
                (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96)
                    ? sqrtPriceLimitX96
                    : step.sqrtPriceNextX96,
                state.liquidity,
                state.amountSpecifiedRemaining,
                fee
            );

            if (exactInput) {
                state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount).toInt256();
                state.amountCalculated = state.amountCalculated.sub(step.amountOut.toInt256());
            } else {
                state.amountSpecifiedRemaining += step.amountOut.toInt256();
                state.amountCalculated = state.amountCalculated.add((step.amountIn + step.feeAmount).toInt256());
            }

            // if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee
            if (cache.feeProtocol > 0) {
                uint256 delta = step.feeAmount / cache.feeProtocol;
                step.feeAmount -= delta;
                state.protocolFee += uint128(delta);
            }

            // update global fee tracker
            if (state.liquidity > 0)
                state.feeGrowthGlobalX128 += FullMath.mulDiv(step.feeAmount, FixedPoint128.Q128, state.liquidity);

            // shift tick if we reached the next price
            if (state.sqrtPriceX96 == step.sqrtPriceNextX96) {
                // if the tick is initialized, run the tick transition
                if (step.initialized) {
                    // check for the placeholder value, which we replace with the actual value the first time the swap
                    // crosses an initialized tick
                    if (!cache.computedLatestObservation) {
                        (cache.tickCumulative, cache.secondsPerLiquidityCumulativeX128) = observations.observeSingle(
                            cache.blockTimestamp,
                            0,
                            slot0Start.tick,
                            slot0Start.observationIndex,
                            cache.liquidityStart,
                            slot0Start.observationCardinality
                        );
                        cache.computedLatestObservation = true;
                    }
                    int128 liquidityNet =
                        ticks.cross(
                            step.tickNext,
                            (zeroForOne ? state.feeGrowthGlobalX128 : feeGrowthGlobal0X128),
                            (zeroForOne ? feeGrowthGlobal1X128 : state.feeGrowthGlobalX128),
                            cache.secondsPerLiquidityCumulativeX128,
                            cache.tickCumulative,
                            cache.blockTimestamp
                        );
                    // if we're moving leftward, we interpret liquidityNet as the opposite sign
                    // safe because liquidityNet cannot be type(int128).min
                    if (zeroForOne) liquidityNet = -liquidityNet;

                    state.liquidity = LiquidityMath.addDelta(state.liquidity, liquidityNet);
                }

                state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
            } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) {
                // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
                state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX96);
            }
        }

        // update tick and write an oracle entry if the tick change
        if (state.tick != slot0Start.tick) {
            (uint16 observationIndex, uint16 observationCardinality) =
                observations.write(
                    slot0Start.observationIndex,
                    cache.blockTimestamp,
                    slot0Start.tick,
                    cache.liquidityStart,
                    slot0Start.observationCardinality,
                    slot0Start.observationCardinalityNext
                );
            (slot0.sqrtPriceX96, slot0.tick, slot0.observationIndex, slot0.observationCardinality) = (
                state.sqrtPriceX96,
                state.tick,
                observationIndex,
                observationCardinality
            );
        } else {
            // otherwise just update the price
            slot0.sqrtPriceX96 = state.sqrtPriceX96;
        }

        // update liquidity if it changed
        if (cache.liquidityStart != state.liquidity) liquidity = state.liquidity;

        // update fee growth global and, if necessary, protocol fees
        // overflow is acceptable, protocol has to withdraw before it hits type(uint128).max fees
        if (zeroForOne) {
            feeGrowthGlobal0X128 = state.feeGrowthGlobalX128;
            if (state.protocolFee > 0) protocolFees.token0 += state.protocolFee;
        } else {
            feeGrowthGlobal1X128 = state.feeGrowthGlobalX128;
            if (state.protocolFee > 0) protocolFees.token1 += state.protocolFee;
        }

        (amount0, amount1) = zeroForOne == exactInput
            ? (amountSpecified - state.amountSpecifiedRemaining, state.amountCalculated)
            : (state.amountCalculated, amountSpecified - state.amountSpecifiedRemaining);

        // do the transfers and collect payment
        if (zeroForOne) {
            if (amount1 < 0) TransferHelper.safeTransfer(token1, recipient, uint256(-amount1));

            uint256 balance0Before = balance0();
            IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
            require(balance0Before.add(uint256(amount0)) <= balance0(), 'IIA');
        } else {
            if (amount0 < 0) TransferHelper.safeTransfer(token0, recipient, uint256(-amount0));

            uint256 balance1Before = balance1();
            IUniswapV3SwapCallback(msg.sender).uniswapV3SwapCallback(amount0, amount1, data);
            require(balance1Before.add(uint256(amount1)) <= balance1(), 'IIA');
        }

        emit Swap(msg.sender, recipient, amount0, amount1, state.sqrtPriceX96, state.liquidity, state.tick);
        slot0.unlocked = true;
    }

    /// @inheritdoc IUniswapV3PoolActions
    function flash(
        address recipient,
        uint256 amount0,
        uint256 amount1,
        bytes calldata data
    ) external override lock noDelegateCall {
        uint128 _liquidity = liquidity;
        require(_liquidity > 0, 'L');

        uint256 fee0 = FullMath.mulDivRoundingUp(amount0, fee, 1e6);
        uint256 fee1 = FullMath.mulDivRoundingUp(amount1, fee, 1e6);
        uint256 balance0Before = balance0();
        uint256 balance1Before = balance1();

        if (amount0 > 0) TransferHelper.safeTransfer(token0, recipient, amount0);
        if (amount1 > 0) TransferHelper.safeTransfer(token1, recipient, amount1);

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

        uint256 balance0After = balance0();
        uint256 balance1After = balance1();

        require(balance0Before.add(fee0) <= balance0After, 'F0');
        require(balance1Before.add(fee1) <= balance1After, 'F1');

        // sub is safe because we know balanceAfter is gt balanceBefore by at least fee
        uint256 paid0 = balance0After - balance0Before;
        uint256 paid1 = balance1After - balance1Before;

        if (paid0 > 0) {
            uint8 feeProtocol0 = slot0.feeProtocol % 16;
            uint256 fees0 = feeProtocol0 == 0 ? 0 : paid0 / feeProtocol0;
            if (uint128(fees0) > 0) protocolFees.token0 += uint128(fees0);
            feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - fees0, FixedPoint128.Q128, _liquidity);
        }
        if (paid1 > 0) {
            uint8 feeProtocol1 = slot0.feeProtocol >> 4;
            uint256 fees1 = feeProtocol1 == 0 ? 0 : paid1 / feeProtocol1;
            if (uint128(fees1) > 0) protocolFees.token1 += uint128(fees1);
            feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - fees1, FixedPoint128.Q128, _liquidity);
        }

        emit Flash(msg.sender, recipient, amount0, amount1, paid0, paid1);
    }

    /// @inheritdoc IUniswapV3PoolOwnerActions
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner {
        require(
            (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) &&
                (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10))
        );
        uint8 feeProtocolOld = slot0.feeProtocol;
        slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4);
        emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1);
    }

    /// @inheritdoc IUniswapV3PoolOwnerActions
    function collectProtocol(
        address recipient,
        uint128 amount0Requested,
        uint128 amount1Requested
    ) external override lock onlyFactoryOwner returns (uint128 amount0, uint128 amount1) {
        amount0 = amount0Requested > protocolFees.token0 ? protocolFees.token0 : amount0Requested;
        amount1 = amount1Requested > protocolFees.token1 ? protocolFees.token1 : amount1Requested;

        if (amount0 > 0) {
            if (amount0 == protocolFees.token0) amount0--; // ensure that the slot is not cleared, for gas savings
            protocolFees.token0 -= amount0;
            TransferHelper.safeTransfer(token0, recipient, amount0);
        }
        if (amount1 > 0) {
            if (amount1 == protocolFees.token1) amount1--; // ensure that the slot is not cleared, for gas savings
            protocolFees.token1 -= amount1;
            TransferHelper.safeTransfer(token1, recipient, amount1);
        }

        emit CollectProtocol(msg.sender, recipient, amount0, amount1);
    }
}
```
:::

申し訳ありません。以下に変数やイベント、関数名をH3で示し、それぞれの要素について説明します。

### factory

**概要**
コントラクトのアドレスを保持する変数。

**詳細**
**UniswapV3Pool**コントラクトをデプロイしたファクトリーコントラクトのアドレスを格納します。

---

### token0

**概要**
トークン0のアドレスを保持する変数。

**詳細**
トークン0は取引ペアの一方であり、この変数はそのアドレスを格納します。

---

### token1

**概要**
トークン1のアドレスを保持する変数。

**詳細**
トークン1は取引ペアのもう一方であり、この変数はそのアドレスを格納します。

---

### fee

**概要**
スワップ手数料のレートを保持する変数。

**詳細**
スワップ時に発生する手数料の割合です。

---

### tickSpacing

**概要**
ティックの間隔を保持する変数。

**詳細**
ティックは価格レベルを表し、隣接するティック間の価格差を示します。

---

### maxLiquidityPerTick

**概要**
ティックごとの最大流動性を保持する変数。

**詳細**
1つのティックごとに許容される最大流動性を示します。

---

### slot0

**概要**
スロット0の情報を保持する構造体。

**詳細**
現在の価格やティック、オブザベーションなどのプールの状態を格納します。

**パラメータ**

- `sqrtPriceX96`
	- 現在の価格の平方根を表す固定小数点数。
- `tick`
	- 現在のティックの値。
- `observationIndex`
	- 最も最近に更新されたオブザベーション配列のインデックス。
- `observationCardinality`
	- 現在格納されているオブザベーションの最大数。
- `observationCardinalityNext`
	- 次に格納するオブザベーションの最大数。
- `feeProtocol`
	- プロトコル手数料の割合を示す整数分母。
- `unlocked`
	- プールのロック状態を示すブール値。

---

### feeGrowthGlobal0X128

**概要**
トークン0の累積手数料成長を保持する変数。

**詳細**
トークン0の累積手数料成長を`128`倍精度で表現します。

---

### feeGrowthGlobal1X128

**概要**
トークン1の累積手数料成長を保持する変数。

**詳細**
トークン1の累積手数料成長を`128`倍精度で表現します。

---

### protocolFees

**概要**
プロトコル手数料の情報を保持する構造体。

**詳細**
トークン0およびトークン1のプロトコル手数料を格納します。

**パラメータ**

- `token0`
	- トークン0に累積されたプロトコル手数料を表す固定小数点数。
- `token1`
	- トークン1に累積されたプロトコル手数料を表す固定小数点数。

---

### liquidity

**概要**
現在の総流動性を保持する変数。

**詳細**
プール全体の総流動性を示します。

---

### ticks

**概要**
ティック情報を保持するマッピング配列。

**詳細**
各ティックの詳細な情報を格納します。

---

### tickBitmap

**概要**
ティックのビットマップを保持するマッピング配列。

**詳細**
ティックが存在することを示すビットを格納します。

---

### positions

**概要**
ポジション情報を保持するマッピング配列。

**詳細**
ポジションの詳細情報を格納します。

---

### observations

**概要**
オブザベーション情報を保持する配列。

**詳細**
価格のオブザベーション情報を格納します。

---

### lock

**概要**
リエントラント攻撃から保護するための修飾子。

**詳細**
特定の関数を呼び出す前にプールがロックされていることを確認し、呼び出し後にプールをアンロックします。

---

### onlyFactoryOwner

**概要**
ファクトリーオーナーのみが呼び出せることを確認するための修飾子。

**詳細**
関数が**UniswapV3**ファクトリーのオーナーによってのみ呼び出されることを確認します。

もちろんです。以下に提供いただいたコード内の各変数、constructor、イベント、関数の説明を示します。

---

### constructor

**概要**
コントラクトのコンストラクタ。

**詳細**
コントラクトがデプロイされる際に実行される初期化関数です。`_tickSpacing`を取得し、`factory`、`token0`、`token1`、`fee`、`tickSpacing`、`maxLiquidityPerTick`を初期化します。

---

### checkTicks

**概要**
ティックの妥当性をチェックする内部関数。

**詳細**
引数の値が問題ないかチェックします。
`tickLower`が`tickUpper`よりも小さく、かつ最小ティックと最大ティックの範囲内であることを確認します。

**引数**
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。

---

### snapshotCumulativesInside

**概要**
指定したティック範囲内の累積データをスナップショットする関数。

**詳細**
引数で指定されたティック範囲内の累積ティックカウントや時間データをスナップショットします。

**引数**
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。

**戻り値**
- `tickCumulativeInside`
	- ティック範囲内の累積ティックカウント。
- `secondsPerLiquidityInsideX128`
	- ティック範囲内の流動性ごとの秒数（`128`倍精度）。
- `secondsInside`
	- ティック範囲内の秒数。

---

### observe

**概要**
過去の時間に対する累積データを取得する関数。

**詳細**
引数で指定された過去の時間に対する累積ティックカウントや時間データを取得します。

**引数**
- `secondsAgos`
	- 過去の時間（秒単位）の配列。

**戻り値**
- `tickCumulatives`
	- 過去の時間に対する累積ティックカウントの配列。
- `secondsPerLiquidityCumulativeX128s`
	- 過去の時間に対する流動性ごとの秒数（`128`倍精度）の配列。

---

### increaseObservationCardinalityNext

**概要**
次のオブザベーションカーディナリティを増やす関数。

**詳細**
次のオブザベーションカーディナリティを増やし、変更があった場合にイベントを発行します。

**引数**
- `observationCardinalityNext`
	- 新しいオブザベーションカーディナリティ。

---

### initialize

**概要**
プールを初期化する関数。

**詳細**
- プールがまだ初期化されていない場合にプールを初期化し、`slot0` を設定します。

**引数**
- `sqrtPriceX96`
	- 初期化する価格の平方根（固定小数点数）。

---

### ModifyPositionParams

**概要**
- ポジションを変更するためのパラメータを格納する構造体。

**詳細**
- ポジションのオーナーアドレス、ティック範囲、流動性の変化などのパラメータを格納します。

**引数**
- `owner`: ポジションのオーナーアドレス
- `tickLower`: 下限ティック値
- `tickUpper`: 上限ティック値
- `liquidityDelta`: 流動性の変化量

---

### _modifyPosition

**概要**
ポジションを変更するための内部関数。

**詳細**
ポジションの変更に伴って発生する`amount0`と量`amount1`を計算し、ポジション情報を更新します。

**引数**
- `params`
	- ポジションの変更パラメータを含む構造体。

**戻り値**
- `position`
	- 変更されたポジションの情報へのストレージポインタ。
- `amount0`
	- プールから徴収されるトークン0の量（負の値の場合はプールが受け取る）。
- `amount1`
	- プールから徴収されるトークン1の量（負の値の場合はプールが受け取る）。

---

### _updatePosition

**概要**
ポジション情報を更新するための内部関数。

**詳細**
ポジション情報を更新し、ティック情報や流動性の変更も行います。

**引数**
- `owner`
	- ポジションのオーナーアドレス。
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。
- `liquidityDelta`
	- 流動性の変化量。
- `tick`
	- 現在のティック値。

**戻り値**
- `position`
	- 更新されたポジションの情報へのストレージポインタ。

---

### mint

**概要**
ポジションを増やして新しい流動性を提供する関数。

**詳細**
指定された範囲のティックに新しい流動性を追加し、プールに対してトークン0とトークン1の量を返します。

**引数**
- `recipient`
	- 流動性を提供するユーザーのアドレス。
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。
- `amount`
	- 提供する流動性の量。
- `data`
	- データ。

**戻り値**
- `amount0`
	- プールから返されるトークン0の量。
- `amount1`
	- プールから返されるトークン1の量。

---

### collect

**概要**
ポジションからトークンを受け取るための関数。

**詳細**
ポジションからトークン0とトークン1を受け取り、受け取った量を返します。

**引数**
- `recipient`
	- トークンを受け取るユーザーのアドレス。
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。
- `amount0Requested`
	- 受け取るトークン0の要求量。
- `amount1Requested`
	- 受け取るトークン1の要求量。

**戻り値**
- `amount0`
	- 受け取ったトークン0の実際の量。
- `amount1`
	- 受け取ったトークン1の実際の量。

---

### burn

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

**詳細**
ポジションから指定された範囲のティックに従って流動性を減少させ、減少したトークン0とトークン1の量を計算します。

**引数**
- `tickLower`
	- 下限ティック値。
- `tickUpper`
	- 上限ティック値。
- `amount`
	- 減少させる流動性の量。

**戻り値**
- `amount0`
	- 減少したトークン0の量。
- `amount1`
	- 減少したトークン1の量。


---

### SwapCache

**概要**
- スワップに関するキャッシュ情報を格納する構造体。

**詳細**
- スワップの実行に関する情報をキャッシュし、計算された値やフラグを保持します。

**引数 or パラメータ**
- `feeProtocol`: 入力トークンのプロトコル料金（プロトコルの手数料）
- `liquidityStart`: スワップの開始時の流動性
- `blockTimestamp`: 現在のブロックのタイムスタンプ
- `tickCumulative`: 現在のティック累積値（初期化済みティックを跨いだ場合にのみ計算）
- `secondsPerLiquidityCumulativeX128`: 現在の流動性累積の秒数（初期化済みティックを跨いだ場合にのみ計算）
- `computedLatestObservation`: ティック累積値と流動性累積の計算が行われたかどうかのフラグ

---

### SwapState

**概要**
スワップのトップレベルの状態を格納する構造体。

**詳細**
スワップのトップレベルの状態を保持し、トークンのスワップ情報や現在の価格などを管理します。

**パラメータ**
- `amountSpecifiedRemaining`
	- スワップされるべき残りの入力/出力トークンの量。
- `amountCalculated`
	- 既にスワップされた出力/入力トークンの量。
- `sqrtPriceX96`
	- 現在の平方根価格（プライス）。
- `tick`
	- 現在のティック値。
- `feeGrowthGlobalX128`
	- 入力トークンのグローバル料金成長（フィー）。
- `protocolFee`
	- プロトコル料金として支払われる入力トークンの量。
- `liquidity`
	- 現在の範囲内の流動性。

---

### StepComputations

**概要**
ステップの計算情報を格納する構造体。

**詳細**
スワップの各ステップにおける計算に関する情報を保持します。

**パラメータ**
- `sqrtPriceStartX96`
	- ステップの開始時の平方根価格（プライス）。
- `tickNext`
	- スワップ方向の現在のティックから次のティックへの移動先。
- `initialized`
	- `tickNext`が初期化されているかどうかのフラグ。
- `sqrtPriceNextX96`
	- 次のティックの平方根価格（プライス）。
- `amountIn`
	- このステップで入力されるトークンの量。
- `amountOut`
	- このステップで出力されるトークンの量。
- `feeAmount`
	- このステップで支払われる手数料の量。

---

### swap

**概要**
トークンのスワップを実行する関数。

**詳細**
指定された条件に基づいて、トークンのスワップを実行します。

**引数**
- `recipient`
	- トークンを受け取るアドレス。
- `zeroForOne`
	- トークンのスワップ方向（`true`: トークン0 → トークン1、`false`: トークン1 → トークン0）。
- `amountSpecified`
	- スワップするトークンの量。
- `sqrtPriceLimitX96`
	- スワップの価格制限。
- `data`
	- データ。

**戻り値**
- `amount0`
	- スワップで得られたトークン0の量。
- `amount1`
	- スワップで得られたトークン1の量。

---

### flash

**概要**

フラッシュスワップを実行する関数。

**詳細**
フラッシュスワップによって指定されたトークンの量をスワップし、手数料を支払い、追加のコールバック処理を行います。
フラッシュスワップにおける手数料や処理後の残高などを計算し、関連するイベントを発行します。

**引数**:
- `recipient`
	- スワップの結果を受け取るアドレス。
- `amount0`
	- スワップするトークン0の量。
- `amount1`
	- スワップするトークン1の量。
- `data`
	- 追加のデータ。

**戻り値**

なし

:::message
### フラッシュスワップとは

**概要**
フラッシュスワップは、DeFi（分散型ファイナンス）プロトコルの一種であり、ユーザーが短時間内にトークンをスワップする操作です。
この操作では、スワップを行うためにトークンを借り入れて即座に返済することで、一時的な価格差を利用して利益を上げることが可能です。

**詳細**
フラッシュスワップは、以下のステップで行われます。

1. **借入**
ユーザーはプロトコルから一時的にトークンを借り入れます。
このトークンを使ってスワップを実行します。

2. **スワップ**
借り入れたトークンを使ってスワップを実行します。
通常、スワップは特定のトークンペアの価格変動を利用して行われます。
この際、スワップによる価格変動を予測して、利益を最大化することが狙いです。

3. **返済**
スワップが成功した場合、ユーザーはスワップで得た利益を使って借り入れたトークンを即座に返済します。
利益を得ることができる一方、返済が行われない場合はトランザクション全体が無効となります。

**仕組み**
フラッシュスワップは、一時的な価格差を利用してトークンを借り入れ、スワップを行い、利益を上げることを目的としています。
通常、フラッシュスワップは瞬時に行われるため、利益を得るためには価格差を正確に予測することが重要です。
また、トランザクションの実行後には必ず借り入れたトークンを返済する必要があります。

**注意点**
フラッシュスワップはデリケートな操作であり、価格の変動やトランザクションの成功に関するリスクが伴います。
また、プロトコルによっては一定の手数料が発生することもあります。
ユーザーは事前にリスクを十分に理解し、慎重に実行する必要があります。

**用途**
フラッシュスワップは、価格差の利益を狙うトレーダーやアービトラージャーにとって有用です。
また、流動性を供給するユーザーもフラッシュスワップを通じて手数料を得ることができる場合があります。

**一般的なプロセス**:
1. トークンを借り入れる。
2. スワップを実行して利益を得る。
3. 借り入れたトークンを即座に返済する。

フラッシュスワップは、分散型ファイナンスの新しい概念であり、ユーザーに短期間での利益を提供する可能性がありますが、リスクも高いため注意が必要です。
:::

---

### setFeeProtocol

**概要**
手数料プロトコルを設定する関数。

**詳細**
スワッププールの手数料プロトコルを設定するための関数。
手数料プロトコルはトークン0とトークン1それぞれに対して設定されます。
プロトコル所有者のみがこの関数を呼び出すことができます。
手数料プロトコルは、スワップ手数料を制御するために使用されます。

**引数**:
- `feeProtocol0`
	- トークン0の手数料プロトコル。
- `feeProtocol1`
	- トークン1の手数料プロトコル。

**戻り値**

なし

---

### collectProtocol

**概要**
プロトコル手数料を回収する関数。

**詳細**
スワッププールのプロトコル手数料を回収するための関数。
プロトコル所有者のみがこの関数を呼び出すことができます。
指定されたトークン0とトークン1の量に基づいて、プロトコル手数料を回収します。
回収された手数料はプロトコル所有者に送信されます。

**引数 or パラメータ**:
- `recipient`
	- 手数料を受け取るアドレス。
- `amount0Requested`
	- 回収するトークン0の量。
- `amount1Requested`
	- 回収するトークン1の量。

**戻り値**:
- `amount0`
	- 実際に回収されたトークン0の量。
- `amount1:
	- 実際に回収されたトークン1の量。

---

# イベント

### OwnerChanged

**概要**
ファクトリーのオーナーが変更された時に発行されるイベント。

**パラメータ**:
- `oldOwner`
	- オーナー変更前のアドレス。
- `newOwner`
	- オーナー変更後のアドレス。

---

### PoolCreated

**概要**
新しいプールが作成された時に発行されるイベント。

**パラメータ**:
- `token0`
	- プール内の最初のトークンのアドレス（アドレスのソート順に基づく）。
- `token1`
	- プール内の2番目のトークンのアドレス（アドレスのソート順に基づく）。
- `fee`
	- プール内での各スワップにおける手数料（0.01%単位で表現）。
- `tickSpacing`
	- 初期化されたティック間の最小数のティック。
- `pool`
	- 作成されたプールのアドレス。

---

### FeeAmountEnabled

**概要**
ファクトリーを通じてプールの作成に使用される新しい手数料が有効になったときに発行されるイベント。

**パラメータ**:
- `fee`
	- 有効になった手数料（0.01%単位で表現）。
- `tickSpacing`
	- 手数料に対応する初期化ティック間隔。

---

# コード

## インターフェース

- `interfaces`ディレクトリ。

## ライブラリ

- `libraries`ディレクトリ。

# 最後に

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

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

https://chaldene.net/

https://qiita.com/cardene
