🦄

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

2023/08/27に公開

はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

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

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

https://www.bunzz.dev/decipher

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

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

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

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

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

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

概要

SwapRouter02コントラクトは、Uniswap分散型取引プロトコルの重要なコンポーネントです。
このコントラクトは、Uniswapプラットフォーム上でトークンを交換するためのルーターとして機能します。

SwapRouter02コントラクトの主な目的は、効率的で安全なトークンの交換をユーザー間で行うことです。
このコントラクトは、トレーダーが最小のスリッページで、最良の利用可能価格でトークンスワップを実行するためのシンプルで使いやすいインターフェースを提供します。

SwapRouter02コントラクトは、以下の重要な役割を担っています。

トークンのスワップ

このコントラクトは、ユーザーがUniswapの流動性プールと連携して、トークンを別のトークンと交換できるようにします。
最適なスワップ経路を計算し、ユーザーの代わりに取引を実行します。

流動性の提供

このコントラクトは、ユーザーがトークンを預け入れることでUniswapプールに流動性を提供できるようにします。
これにより、トークンのスワップに十分な流動性が確保されます。

価格の計算

このコントラクトは、Uniswapプラットフォーム上のトークンの正確で最新の価格情報を提供します。
これにより、トレードを実行する際にユーザーが価格情報を元に判断ができます。

セキュリティと効率性

SwapRouter02コントラクトは、トークンのスワップのセキュリティと効率性を確保するよう設計されています。
フロントランニングなどの操作を防ぐためのさまざまなメカニズムを組み込み、スリッページを最小化し流動性を最大化するために取引の経路を最適化します。

以上のように、SwapRouter02コントラクトはUniswapプラットフォーム上でスムーズで安全なトークンスワップを可能にする重要な役割を果たしています。
ユーザーに信頼性のある効率的な方法を提供し、同時に分散型取引エコシステムの誠実さと安定性を確保しています。

使い方

SwapRouter02コントラクトは、Uniswapの分散型取引所においてトークンのスワップを行うためのスマートコントラクトです。
このコントラクトはISwapRouter02インターフェースの実装であり、トークンのスワップに関するさまざまな機能を提供します。

コントラクトの目的

SwapRouter02コントラクトの目的は、ユーザーがUniswapの分散型取引所上でトークンをスワップできるようにすることです。
Uniswapの流動性プールを利用して、トークンスワップを便利かつ効率的に実行する手段を提供します。

手順

Uniswap上でトークンをスワップするためには、以下の手順が必要です。

  1. SwapRouter02コントラクトをデプロイします。
  2. コントラクトに対してトークンの使用権限を許可します。
  3. exactInputまたはexactOutput関数を呼び出して、トークンスワップを実行します。

これにより、ユーザーは簡単にトークンスワップを行うことができ、Uniswapの流動性プールを活用してスムーズなトークンの交換が可能となります。

関数

SwapRouter02コントラクトは、以下のトークンスワップに関する関数を提供しています。

exactInput

入力量を指定してトークンスワップを実行します。
最小の出力量も指定します。
指定した入力量に対して、それに対応する最小の出力量を保証してトークンスワップを実行します。

exactOutput

出力量を指定してトークンスワップを実行します。
最大の入力量も指定します。
指定した出力量を達成するために必要な最大の入力量を計算し、それを使用してトークンスワップを行います。

refundETH

コントラクトに送信された余剰のETHを返金します。
トークンスワップの際に不要なETHが送信された場合に、それを返金するための関数です。

WETH9

WETH9トークンコントラクトのアドレスを返します。
この関数を使用して、WETH9(Wrapped Ether) トークンのアドレスを取得することができます。

使い方

正確な入力量でのスワップの実行。

  1. SwapRouter02コントラクトに、指定した入力トークン量を使用する許可を与えます。
  2. exactInput関数を呼び出し、以下の引数を指定します。
    • amountIn
      • 正確な入力トークン量。
    • amountOutMin
      • 最小の出力トークン量。
      • スワップ時の最低限の出力量を保証します。
    • path
      • トークンスワップのためのトークンアドレスの配列。
      • 指定したトークン同士のスワップ経路を示します。
    • to
      • 出力トークンを受け取るアドレス。
    • deadline
      • スワップが実行される期限。

正確な出力量でのスワップの実行。

  1. SwapRouter02コントラクトに、最大の入力トークン量を使用する許可を与えます。
  2. exactOutput関数を呼び出し、以下の引数を指定します。
    • amountOut
      • 正確な出力トークン量。
    • amountInMax
      • 最大の入力トークン量。
      • スワップ時に使用される最大の入力量を示します。
    • path
      • トークンスワップのためのトークンアドレスの配列。
      • 指定したトークン同士のスワップ経路を示します。
    • to
      • 出力トークンを受け取るアドレス。
    • deadline
      • スワップが実行される期限。

イベント

Swap

トークンスワップが実行されたことを示すイベント。

  • sender
    • スワップを開始したアドレス。
  • amount0In
    • トークン0の入力量。
  • amount1In
    • トークン1の入力量。
  • amount0Out
    • トークン0の出力量。
  • amount1Out
    • トークン1の出力量。
  • to
    • 出力トークンを受け取ったアドレス。

RefundETH

余剰のETHが返金されたことを示すイベント。

  • amount
    • 返金されたETHの量。
  • to
    • 返金を受け取ったアドレス。

関連ERC/EIP

https://chaldene.net/erc20

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

https://chaldene.net/erc721

https://chaldene.net/erc721-extension

パラメータ

_factoryV2

V2ファクトリーコントラクトのアドレスです。

  • V2ファクトリーコントラクトは、Uniswap V2のプールを作成および管理する役割を果たします。
  • トークンの流動性プールを作成し、トークンのスワップを可能にします。

_factoryV3

V3ファクトリーコントラクトのアドレスです。

  • V3ファクトリーコントラクトは、Uniswap V3のプールを作成および管理するためのコントラクトです。
  • V3ではより精密な価格帯を設定し、トークンの効率的なスワップを実現します。

_positionManager

ポジションマネージャーコントラクトのアドレスです。

  • ポジションマネージャーコントラクトは、Uniswap V3の流動性プール内のポジションを管理する役割を果たします。
  • ユーザーはこのコントラクトを介してトークンの流動性を提供および管理することができます。

_WETH9

WETH9コントラクトのアドレスです。

  • WETH9(Wrapped Ether) は、イーサリアムのネイティブトークンETHERC20トークン化したバージョンです。
  • ETHをトークンとして取り扱うことができるため、分散型取引所などでトークン同士のスワップを実行する際に便利に使用されます。

コントラクト

SwapRouter02

SwapRouter02
contract SwapRouter02 is ISwapRouter02, V2SwapRouter, V3SwapRouter, ApproveAndCall, MulticallExtended, SelfPermit {
    constructor(
        address _factoryV2,
        address factoryV3,
        address _positionManager,
        address _WETH9
    ) ImmutableState(_factoryV2, _positionManager) PeripheryImmutableState(factoryV3, _WETH9) {}
}

ISwapRouter02

ISwapRouter02インターフェースを実装しています。
これにより、SwapRouter02コントラクトはISwapRouter02のメソッドを含むインターフェースの機能を提供します。

V2SwapRouter

Uniswap V2のスワップルーター機能を提供するコントラクトです。
V2SwapRouterによって、Uniswap V2のバージョン固有のスワップ関連機能が実装されています。

V3SwapRouter

Uniswap V3のスワップルーター機能を提供するコントラクトです。
V3SwapRouterによって、Uniswap V3のバージョン固有のスワップ関連機能が実装されています。

ApproveAndCall

ユーザーがコントラクトに対して承認と同時にコールを実行できる機能を提供します。
これにより、スワップ操作がより効率的に行えます。

MulticallExtended

複数の関数コールを1つのトランザクションで実行できる機能を提供します。
複数の操作を1度のトランザクションでまとめて行えるため、ガスの節約に寄与します。

SelfPermit

イーサリアムのEIP-2612に基づいて、コントラクト自体に対してトークンの承認を行う機能を提供します。

constructor

コントラクトのコンストラクタ関数です。
Uniswap V2およびV3のファクトリーコントラクトのアドレス、ポジションマネージャーコントラクトのアドレス、WETH9トークンコントラクトのアドレスを受け取ります。
これらのアドレスはコントラクトの初期化時に設定され、スワップ操作などに使用されます。

V2SwapRouter

以下の記事に詳しくまとめています。

https://zenn.dev/cryptogames/articles/a958a6870d9016

V3SwapRouter

以下の記事に詳しくまとめています。

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

ApproveAndCall

トークンの承認と同時に特定の関数を実行できる機能を提供する抽象コントラクト。
ユーザーがトークンの承認と関数の呼び出しを同時に行うための仕組みを提供します。
これにより、トークンの承認と操作を1つのトランザクションで結びつけることができ、操作の効率が向上します。


tryApprove

tryApprove
function tryApprove(address token, uint256 amount) private returns (bool) {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.approve.selector, positionManager, amount));
        return success && (data.length == 0 || abi.decode(data, (bool)));
}

概要
指定したトークンに対してPositionManagerへの承認を実行する関数。

引数

  • token
    • 承認するトークンのアドレス。
  • amount
    • 承認するトークンの量。

戻り値

  • bool
    • 承認が成功したかどうか。

getApprovalType

getApprovalType
function getApprovalType(address token, uint256 amount) external override returns (ApprovalType) {
        // check existing approval
        if (IERC20(token).allowance(address(this), positionManager) >= amount) return ApprovalType.NOT_REQUIRED;

        // try type(uint256).max / type(uint256).max - 1
        if (tryApprove(token, type(uint256).max)) return ApprovalType.MAX;
        if (tryApprove(token, type(uint256).max - 1)) return ApprovalType.MAX_MINUS_ONE;

        // set approval to 0 (must succeed)
        require(tryApprove(token, 0));

        // try type(uint256).max / type(uint256).max - 1
        if (tryApprove(token, type(uint256).max)) return ApprovalType.ZERO_THEN_MAX;
        if (tryApprove(token, type(uint256).max - 1)) return ApprovalType.ZERO_THEN_MAX_MINUS_ONE;

        revert();
}

概要
トークンの承認タイプを取得する関数。

詳細
特定のトークンに対して承認が必要かどうか、どのタイプの承認が必要かを判断します。
複数の承認方法を試みて、適切な承認タイプを返します。

引数

  • token
    • 承認対象のトークンのアドレス。
  • amount
    • 承認するトークンの量。

戻り値

  • ApprovalType
    • 承認タイプ(NOT_REQUIREDMAXMAX_MINUS_ONEZERO_THEN_MAXZERO_THEN_MAX_MINUS_ONE)。

approveMax

approveMax
function approveMax(address token) external payable override {
        require(tryApprove(token, type(uint256).max));
}

概要
最大のトークン承認を行う関数。

引数

  • token
    • 承認対象のトークンのアドレス。

approveMaxMinusOne

approveMaxMinusOne
function approveMaxMinusOne(address token) external payable override {
        require(tryApprove(token, type(uint256).max - 1));
}

概要
最大値から1引いたトークン量を承認する関数。

引数

  • token
    • 承認対象のトークンのアドレス。

approveZeroThenMax

approveZeroThenMax
function approveZeroThenMax(address token) external payable override {
        require(tryApprove(token, 0));
        require(tryApprove(token, type(uint256).max));
}

概要
最初にトークンの承認を0に設定し、その後最大のトークン承認を行う関数。

引数

  • token
    • 承認対象のトークンのアドレス。

approveZeroThenMaxMinusOne

approveZeroThenMaxMinusOne
function approveZeroThenMaxMinusOne(address token) external payable override {
        require(tryApprove(token, 0));
        require(tryApprove(token, type(uint256).max - 1));
}

概要
最初にトークンの承認を0に設定し、その後最大値から1引いた量のトークン承認を行う関数。

引数

  • token
    • 承認対象のトークンのアドレス。

callPositionManager

callPositionManager
function callPositionManager(bytes memory data) public payable override returns (bytes memory result) {
        bool success;
        (success, result) = positionManager.call(data);

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

概要
PositionManagerコントラクトの特定の関数を呼び出す関数。

詳細
PositionManagerコントラクトの特定の関数を呼び出し、その結果を返します。
関数呼び出しに失敗した場合、エラーメッセージが表示されます。

引数

  • data
    • 呼び出す関数と引数のデータ。

戻り値

  • bytes memory
    • 関数の実行結果。

balanceOf

balanceOf
function balanceOf(address token) private view returns (uint256) {
        return IERC20(token).balanceOf(address(this));
}

概要
指定したトークンの残高を取得する関数。

引数

  • token
    • 残高を取得するトークンのアドレス。

戻り値

  • uint256
    • トークンの残高。

mint

mint
function mint(MintParams calldata params) external payable override returns (bytes memory result) {
        return
            callPositionManager(
                abi.encodeWithSelector(
                    INonfungiblePositionManager.mint.selector,
                    INonfungiblePositionManager.MintParams({
                        token0: params.token0,
                        token1: params.token1,
                        fee: params.fee,
                        tickLower: params.tickLower,
                        tickUpper: params.tickUpper,
                        amount0Desired: balanceOf(params.token0),
                        amount1Desired: balanceOf(params.token1),
                        amount0Min: params.amount0Min,
                        amount1Min: params.amount1Min,
                        recipient: params.recipient,
                        deadline: type(uint256).max // deadline should be checked via multicall
                    })
                )
            );
}

概要
Uniswap V3のポジションを新たに作成する関数。

詳細
この関数は、指定されたパラメータに基づいてUniswap V3のポジションを新たに作成します。
トークン0とトークン1の情報、手数料、トレーディングペアの価格範囲(ティック範囲)、希望のトークン0量とトークン1量、最小のトークン0量とトークン1量、受取人のアドレスなどが指定されます。トークンの承認は、前述の方法で行われます。

引数

  • params
    • 作成するポジションのパラメータが含まれる構造体。

戻り値

  • result
    • ポジション作成の結果を示すデータ。

increaseLiquidity

increaseLiquidity
function increaseLiquidity(IncreaseLiquidityParams calldata params)
        external
        payable
        override
        returns (bytes memory result)
    {
        return
            callPositionManager(
                abi.encodeWithSelector(
                    INonfungiblePositionManager.increaseLiquidity.selector,
                    INonfungiblePositionManager.IncreaseLiquidityParams({
                        tokenId: params.tokenId,
                        amount0Desired: balanceOf(params.token0),
                        amount1Desired: balanceOf(params.token1),
                        amount0Min: params.amount0Min,
                        amount1Min: params.amount1Min,
                        deadline: type(uint256).max // deadline should be checked via multicall
                    })
                )
            );
}

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

詳細
指定されたパラメータに基づいて既存のUniswap V3のポジションに流動性を追加します。
ポジションのトークン0とトークン1の情報、追加するトークン0量とトークン1量、最小のトークン0量とトークン1量が指定されます。トークンの承認は、前述の方法で行われます。

引数

  • params
    • 流動性を追加するポジションのパラメータが含まれる構造体。

戻り値

  • result
    • 流動性追加の結果を示すデータ。

MulticallExtended

multicall

multicall
function multicall(uint256 deadline, bytes[] calldata data)
        external
        payable
        override
        checkDeadline(deadline)
        returns (bytes[] memory)
    {
        return multicall(data);
}

概要
複数のコールをまとめて実行する関数。

詳細
この関数は、指定されたデータの配列に含まれる複数のコールをまとめて実行します。
デッドライン(締め切り時刻)や前のブロックハッシュの確認も行われます。
実行結果は、それぞれのコールに対応する結果の配列として返されます。

引数

  • deadline
    • 実行のデッドライン(締め切り時刻)。
  • data
    • 実行するコールのデータの配列。

戻り値

  • bytes[] memory
    • 各コールの実行結果を示すデータの配列。

multicall (previousBlockhash version)

multicall
function multicall(bytes32 previousBlockhash, bytes[] calldata data)
        external
        payable
        override
        checkPreviousBlockhash(previousBlockhash)
        returns (bytes[] memory)
    {
        return multicall(data);
}

概要
複数のコールをまとめて実行する関数。

詳細
この関数は、指定されたデータの配列に含まれる複数のコールをまとめて実行します。
この時、前のブロックハッシュの確認が行われます。
実行結果は、それぞれのコールに対応する結果の配列として返されます。

引数

  • previousBlockhash
    • 実行の前のブロックハッシュ。
  • data
    • 実行するコールのデータの配列。

戻り値

  • bytes[] memory
    • 各コールの実行結果を示すデータの配列。

ImmutableState

factoryV2

factoryV2
address public immutable override factoryV2;

概要
Uniswap V2のファクトリーコントラクトのアドレスを格納する変数。

詳細
Uniswap V2のファクトリーコントラクトのアドレスを不変で公開可能な形で変数に格納しています。
Uniswap V2のファクトリーコントラクトは、トークンの取引を管理するための基盤となるコントラクトです。


positionManager

positionManager
address public immutable override positionManager;

概要
ポジションマネージャーコントラクトのアドレスを格納する変数。

詳細
この変数はポジションマネージャーコントラクトのアドレスを不変で公開可能な形で変数に格納しています。
ポジションマネージャーコントラクトは、Uniswap V3のポジションを管理するためのコントラクトです。


constructor

constructor
constructor(address _factoryV2, address _positionManager) {
        factoryV2 = _factoryV2;
        positionManager = _positionManager;
}

概要
コントラクトのコンストラクタは初期化時に実行される関数で、変数の初期値を設定します。

詳細
このコンストラクタはfactoryV2positionManager変数を初期化します。
コントラクトをデプロイする際に提供されたアドレスがそれぞれの変数に設定されます。

引数:

  • _factoryV2
    • Uniswap V2のファクトリーコントラクトのアドレス。
  • _positionManager
    • ポジションマネージャーコントラクトのアドレス。

PeripheryPaymentsWithFeeExtended

unwrapWETH9WithFee

unwrapWETH9WithFee
function unwrapWETH9WithFee(
        uint256 amountMinimum,
        uint256 feeBips,
        address feeRecipient
    ) external payable override {
        unwrapWETH9WithFee(amountMinimum, msg.sender, feeBips, feeRecipient);
}

概要
WETH9トークンを手数料付きでアンラップする関数。

詳細
指定された最小量のWETH9トークンをアンラップします。
手数料が差し引かれた後、残りのトークンが指定されたアドレスに送信されます。

引数:

  • amountMinimum
    • 最小アンラップ量として要求されるWETH9トークンの量。
  • feeBips
    • 手数料として支払われる基準ポイントの量。
  • feeRecipient
    • 手数料の受取先となるアドレス。

戻り値: なし


sweepTokenWithFee

sweepTokenWithFee
function sweepTokenWithFee(
        address token,
        uint256 amountMinimum,
        uint256 feeBips,
        address feeRecipient
    ) external payable override {
        sweepTokenWithFee(token, amountMinimum, msg.sender, feeBips, feeRecipient);
}

概要
指定されたトークンを手数料付きでスウィープする関数。

詳細
指定されたトークンをアンラップして手数料が差し引かれた後、残りのトークンが指定されたアドレスに送信されます。

引数:

  • token: スウィープされる対象のトークンのアドレス。
  • amountMinimum: 最小スウィープ量として要求されるトークンの量。
  • feeBips: 手数料として支払われる基準ポイントの量。
  • feeRecipient: 手数料の受取先となるアドレス。

戻り値: なし

PeripheryPaymentsExtended

unwrapWETH9

unwrapWETH9
function unwrapWETH9(uint256 amountMinimum) external payable override {
        unwrapWETH9(amountMinimum, msg.sender);
}

概要
ETHに変換されたWETH(Wrapped Ether) トークンをアンラップし、元のETHに戻す関数。

詳細
ユーザーがWETHトークンをアンラップし、最低限必要な量のETHを受け取るために使用されます。
アンラップするWETHの最小量を指定する必要があります。

引数:

  • amountMinimum
    • アンラップする最小のWETH量。

wrapETH

wrapETH
function wrapETH(uint256 value) external payable override {
        IWETH9(WETH9).deposit{value: value}();
}

概要
ETHWETH(Wrapped Ether) トークンに変換する関数。

引数:

  • value
    • 変換するETHの量。

sweepToken

sweepToken
function sweepToken(address token, uint256 amountMinimum) external payable override {
        sweepToken(token, amountMinimum, msg.sender);
}

概要
指定されたトークンを指定のアドレスに送る関数。

詳細
ユーザーが指定されたトークンを指定されたアドレスに送るために使用されます。
送信するトークンのアドレスと送信する最小量を指定する必要があります。

引数:

  • token
    • 送信するトークンのアドレス。
  • amountMinimum
    • 送信する最小のトークン量。

pull

pull
function pull(address token, uint256 value) external payable override {
        TransferHelper.safeTransferFrom(token, msg.sender, address(this), value);
}

概要
他のアドレスからトークンを受け取る関数。

詳細
他のアドレスからトークンを受け取るために使用されます。
受け取るトークンのアドレスと受け取る量を指定する必要があります。

引数:

  • token
    • 受け取るトークンのアドレス。
  • value
    • 受け取るトークンの量。

OracleSlippage

getBlockStartingAndCurrentTick

getBlockStartingAndCurrentTick
function getBlockStartingAndCurrentTick(IUniswapV3Pool pool)
        internal
        view
        returns (int24 blockStartingTick, int24 currentTick)
    {
        uint16 observationIndex;
        uint16 observationCardinality;
        (, currentTick, observationIndex, observationCardinality, , , ) = pool.slot0();

        // 2 observations are needed to reliably calculate the block starting tick
        require(observationCardinality > 1, 'NEO');

        // If the latest observation occurred in the past, then no tick-changing trades have happened in this block
        // therefore the tick in `slot0` is the same as at the beginning of the current block.
        // We don't need to check if this observation is initialized - it is guaranteed to be.
        (uint32 observationTimestamp, int56 tickCumulative, , ) = pool.observations(observationIndex);
        if (observationTimestamp != uint32(_blockTimestamp())) {
            blockStartingTick = currentTick;
        } else {
            uint256 prevIndex = (uint256(observationIndex) + observationCardinality - 1) % observationCardinality;
            (uint32 prevObservationTimestamp, int56 prevTickCumulative, , bool prevInitialized) =
                pool.observations(prevIndex);

            require(prevInitialized, 'ONI');

            uint32 delta = observationTimestamp - prevObservationTimestamp;
            blockStartingTick = int24((tickCumulative - prevTickCumulative) / delta);
        }
}

概要
現在のブロックの開始時点と現在の時点のティック(価格の変動幅)を返す関数。

詳細
指定されたUniswap V3プールにおける現在のティックと開始時点のティックを取得します。
これにより、ブロック内での価格変動を把握し、スリッページを評価するのに役立ちます。

引数:

  • pool
    • 操作対象のUniswap V3プールのアドレス。

戻り値:

  • blockStartingTick
    • ブロックの開始時点のティック(価格の変動幅)。
  • currentTick: 現在のティック(価格の変動幅)。

getPoolAddress

getPoolAddress
function getPoolAddress(
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal view virtual returns (IUniswapV3Pool pool) {
        pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)));
}

概要
プールのアドレスを取得する関数。

詳細
トークンA、トークンB、および手数料率を指定してプールのアドレスを取得します。

引数:

  • tokenA
    • トークンAのアドレス。
  • tokenB
    • トークンBのアドレス。
  • fee
    • プールの手数料率。

戻り値:

  • pool
    • 取得されたプールのインターフェース。

getSyntheticTicks

getSyntheticTicks
function getSyntheticTicks(bytes memory path, uint32 secondsAgo)
        internal
        view
        returns (int256 syntheticAverageTick, int256 syntheticCurrentTick)
    {
        bool lowerTicksAreWorse;

        uint256 numPools = path.numPools();
        address previousTokenIn;
        for (uint256 i = 0; i < numPools; i++) {
            // this assumes the path is sorted in swap order
            (address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
            IUniswapV3Pool pool = getPoolAddress(tokenIn, tokenOut, fee);

            // get the average and current ticks for the current pool
            int256 averageTick;
            int256 currentTick;
            if (secondsAgo == 0) {
                // we optimize for the secondsAgo == 0 case, i.e. since the beginning of the block
                (averageTick, currentTick) = getBlockStartingAndCurrentTick(pool);
            } else {
                (averageTick, ) = OracleLibrary.consult(address(pool), secondsAgo);
                (, currentTick, , , , , ) = IUniswapV3Pool(pool).slot0();
            }

            if (i == numPools - 1) {
                // if we're here, this is the last pool in the path, meaning tokenOut represents the
                // destination token. so, if tokenIn < tokenOut, then tokenIn is token0 of the last pool,
                // meaning the current running ticks are going to represent tokenOut/tokenIn prices.
                // so, the lower these prices get, the worse of a price the swap will get
                lowerTicksAreWorse = tokenIn < tokenOut;
            } else {
                // if we're here, we need to iterate over the next pool in the path
                path = path.skipToken();
                previousTokenIn = tokenIn;
            }

            // accumulate the ticks derived from the current pool into the running synthetic ticks,
            // ensuring that intermediate tokens "cancel out"
            bool add = (i == 0) || (previousTokenIn < tokenIn ? tokenIn < tokenOut : tokenOut < tokenIn);
            if (add) {
                syntheticAverageTick += averageTick;
                syntheticCurrentTick += currentTick;
            } else {
                syntheticAverageTick -= averageTick;
                syntheticCurrentTick -= currentTick;
            }
        }

        // flip the sign of the ticks if necessary, to ensure that the lower ticks are always worse
        if (!lowerTicksAreWorse) {
            syntheticAverageTick *= -1;
            syntheticCurrentTick *= -1;
        }
}

概要
指定されたパスと時間から、合成的な時加重平均ティックを取得する関数。

詳細
指定されたパス(トークン間の経路)と時間から、指定時間内の合成的な時加重平均ティックを取得します。
また、現在のティックも取得します。
この合成ティックは、スリッページの評価に使用されます。

引数:

  • path
    • トークン間の経路を示すバイト列。
  • secondsAgo
    • 遡る時間(秒単位)。

戻り値:

  • syntheticAverageTick
    • 合成的な時加重平均ティック。
  • syntheticCurrentTick
    • 現在のティック。

toInt24

toInt24
function toInt24(int256 y) private pure returns (int24 z) {
        require((z = int24(y)) == y);
}

概要
int256int24にキャストする関数。

詳細
int256int24にキャストします。
オーバーフローやアンダーフローが発生した場合、リバート(エラーを発生)します。

引数:

  • y
    • キャストされるint256の値。

戻り値:

  • z
    • キャストされたint24の値。

getSyntheticTicks

getSyntheticTicks
function getSyntheticTicks(
        bytes[] memory paths,
        uint128[] memory amounts,
        uint32 secondsAgo
    ) internal view returns (int256 averageSyntheticAverageTick, int256 averageSyntheticCurrentTick) {
        require(paths.length == amounts.length);

        OracleLibrary.WeightedTickData[] memory weightedSyntheticAverageTicks =
            new OracleLibrary.WeightedTickData[](paths.length);
        OracleLibrary.WeightedTickData[] memory weightedSyntheticCurrentTicks =
            new OracleLibrary.WeightedTickData[](paths.length);

        for (uint256 i = 0; i < paths.length; i++) {
            (int256 syntheticAverageTick, int256 syntheticCurrentTick) = getSyntheticTicks(paths[i], secondsAgo);
            weightedSyntheticAverageTicks[i].tick = toInt24(syntheticAverageTick);
            weightedSyntheticCurrentTicks[i].tick = toInt24(syntheticCurrentTick);
            weightedSyntheticAverageTicks[i].weight = amounts[i];
            weightedSyntheticCurrentTicks[i].weight = amounts[i];
        }

        averageSyntheticAverageTick = OracleLibrary.getWeightedArithmeticMeanTick(weightedSyntheticAverageTicks);
        averageSyntheticCurrentTick = OracleLibrary.getWeightedArithmeticMeanTick(weightedSyntheticCurrentTicks);
}

概要
複数のパスにおける合成的な時加重平均ティックを取得し、それらのティックの加重平均を計算する関数。

詳細
複数のパスと指定された時間内でのティックの合成的な時加重平均を取得し、それらのティックの加重平均を計算します。
異なる経路でのスリッページを評価する際に使用されます。
すべてのパスは同じトークンで始まり終わる必要があります。

引数:

  • paths
    • 複数のトークン間の経路を示すバイト列の配列。
  • amounts
    • 各経路に対する入力量(量の配列)。
  • secondsAgo
    • 遡る時間(秒単位)。

戻り値:

  • averageSyntheticAverageTick
    • 加重平均された合成的な時加重平均ティック。
  • averageSyntheticCurrentTick
    • 加重平均された現在のティック。

checkOracleSlippage

checkOracleSlippage
function checkOracleSlippage(
        bytes memory path,
        uint24 maximumTickDivergence,
        uint32 secondsAgo
    ) external view override {
        (int256 syntheticAverageTick, int256 syntheticCurrentTick) = getSyntheticTicks(path, secondsAgo);
        require(syntheticAverageTick - syntheticCurrentTick < maximumTickDivergence, 'TD');
}

概要
スリッページを評価し、指定されたティックの最大乖離を超えるかどうかを確認する関数。

詳細
指定されたパスと指定された時間内でのティックの最大乖離を評価し、最大乖離を超える場合にエラーを発生させます。
スリッページを制御するために使用されます。

引数:

  • path
    • トークン間の経路を示すバイト列。
  • maximumTickDivergence
    • 最大ティック乖離。

離値。

  • secondsAgo
    • 遡る時間(秒単位)。

checkOracleSlippage

checkOracleSlippage
function checkOracleSlippage(
        bytes[] memory paths,
        uint128[] memory amounts,
        uint24 maximumTickDivergence,
        uint32 secondsAgo
    ) external view override {
        (int256 averageSyntheticAverageTick, int256 averageSyntheticCurrentTick) =
            getSyntheticTicks(paths, amounts, secondsAgo);
        require(averageSyntheticAverageTick - averageSyntheticCurrentTick < maximumTickDivergence, 'TD');
}

概要
複数のパスにおけるスリッページを評価し、指定されたティックの最大乖離を超えるかどうかを確認する関数。

詳細
複数のパスと指定された時間内でのティックの最大乖離を評価し、最大乖離を超える場合にエラーを発生させます。
異なる経路でのスリッページを評価する際に使用されます。

引数:

  • paths
    • 複数のトークン間の経路を示すバイト列の配列。
  • amounts
    • 各経路に対する入力量(量の配列)。
  • maximumTickDivergence
    • 最大ティック乖離値。
  • secondsAgo
    • 遡る時間(秒単位)。

PeripheryValidationExtended

checkPreviousBlockhash

checkPreviousBlockhash
modifier checkPreviousBlockhash(bytes32 previousBlockhash) {
        require(blockhash(block.number - 1) == previousBlockhash, 'Blockhash');
        _;
}

checkPreviousBlockhashは、関数が実行される前に特定の条件をチェックし、条件が満たされていれば関数の実行を続行するという制御構造を提供します。

具体例を交えて説明します。

abstract contract PeripheryValidationExtended is PeripheryValidation {
    modifier checkPreviousBlockhash(bytes32 previousBlockhash) {
        require(blockhash(block.number - 1) == previousBlockhash, 'Blockhash');
        _; // チェックを通過したら関数本体を実行
    }
}

この checkPreviousBlockhash修飾子は、関数が呼び出される前に前のブロックのハッシュが指定されたハッシュと一致するかどうかを確認します。
もし一致しない場合、関数の呼び出しは失敗し、エラーメッセージ**'Blockhash'** と共に失敗の理由を示します。

例を挙げてみましょう。

contract MyContract is PeripheryValidationExtended {
    bytes32 private previousBlockhash;

    function setPreviousBlockhash(bytes32 _blockhash) external {
        previousBlockhash = _blockhash;
    }

    function performAction() external checkPreviousBlockhash(previousBlockhash) {
        // ブロックハッシュのチェックをパスした場合にのみ実行される
        // ...
    }
}

上記のMyContractコントラクトでは、performAction関数が checkPreviousBlockhash(previousBlockhash)修飾子を使用しています。
このmodifierは、指定されたpreviousBlockhashと前のブロックのハッシュが一致するかを確認し、一致していれば関数を実行します。
一致しない場合、関数は実行されずにエラーが発生します。

これにより、前のブロックのハッシュが特定の値と一致することが必要な場面で、関数の実行条件を確認することができるようになります。
:::

イベント

特になし。

コード

インターフェース

  • SwapRouter02/contracts/interfaces

ベースコントラクト

  • SwapRouter02/contracts/base

ライブラリ

  • SwapRouter02/contracts/libraries

uniswap core

  • SwapRouter02/@uniswap

openzeppelin contract

  • SwapRouter02/@openzeppelin`

最後に

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

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

https://chaldene.net/

https://qiita.com/cardene

DeCipher |"Read me" for All of Contracts

Discussion