はじめに

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

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

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

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

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

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

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

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

概要

SwapRouterコントラクトは、Uniswap V3プロトコルの重要な部分で、Ethereumブロックチェーン上で効率的で安全なトークンスワップを実行します。 このコントラクトは、流動性プロバイダーとトレーダーの間でトークンを最適な価格で交換します。

SwapRouterコントラクトの主な目的は、複数の流動性プール間で最もコスト効率の良いルートを見つけてスワップを行うことです。 これには、SafeCast、TickMath、Pathなどのサポートコントラクトやライブラリを組み合わせて、ユーザーが最も有利な価格でトークンをスワップできるようにサポートしています。複雑な計算やトークン変換の問題を効果的に解決し、安全かつ効率的なトークンスワップを実現する役割を果たしています。

SafeCast 数値のキャスト(変換)を安全に行います。 ブロックチェーン上では数値の扱いに注意が必要で、オーバーフローやアンダーフローなどの問題が発生する可能性があります。 SafeCastは、数値変換時にこれらの問題を回避し、正確な計算を保証します。

TickMath トークン価格や流動性プールの価格設定に関連する複雑な計算を行うためのライブラリです。 トークン価格の変動や流動性の変化を考慮して、最適な価格でトークンを交換するための計算を行います。

Path 複数の流動性プールをまたいで最適なスワップルートを見つけるためのツールです。 異なるトークン間のスワップは、複数のプールを経由して行われる場合があります。 Pathは、これらのプール間を最適に結ぶルートを計算し、最良のトークン変換経路を提供します。

Uniswap V3は、異なるトークンを交換するための流動性プールを提供するプラットフォームです。 これらのプールにはトークンが保管され、トレーダーはこれらのプールを利用してトークンをスワップできます。SwapRouterコントラクトは、これらのプールとのやり取りをするために、IUniswapV3Poolというインターフェースを使用します。

IUniswapV3Poolインターフェースは、プールとやり取りするための一連のメソッドや機能を定義しています。 これにより、SwapRouterコントラクトは以下のことができます。

  1. 流動性にアクセスする IUniswapV3Poolインターフェースを介して、プール内に保管されているトークンの量や価格などの情報にアクセスできます。 これにより、最適なトークンスワップを計算するためのデータを取得できます。

  2. スワップを実行する IUniswapV3Poolインターフェースを使用して、実際のトークンスワップを実行できます。 ユーザーが要求したトークン交換をプール内の情報に基づいて実行し、結果を提供します。

  3. プールの状態を管理する Uniswap V3プールは動的に変化する可能性があり、トークン価格や流動性の量が変動します。 IUniswapV3Poolインターフェースを使用して、プールの状態を監視し、最新のデータを取得して管理することができます。

  4. アクションとイベントを処理する Uniswap V3プールはトークンスワップ以外にもさまざまなアクションやイベントを管理します。 IUniswapV3Poolインターフェースを通じて、これらのアクションやイベントを適切に処理し、トークンスワップに関連する動作を管理できます。

SwapRouterコントラクトが安全で信頼性の高いスワッププロセスを実行するために、SwapRouterコントラクトにはいくつかの検証メカニズムが組み込まれています。 ユーザーの入力が許容範囲内であることを確認するために、PeripheryValidationコントラクトを使用してユーザー入力を検証します。 さらに、外部コントラクトとの相互作用に伴う潜在的なリスクを軽減するために、CallbackValidationコントラクトを使用してコールバック関数を検証します。

PeripheryValidation ユーザーが提供する入力が許容範囲内であるかどうかを確認するための仕組みです。 トークンスワップの際、ユーザーは入力としてスワップするトークンの数量や価格帯などを提供します。 これらの入力が不適切である場合、予期しない問題が発生する可能性があります。PeripheryValidationコントラクトは、ユーザーが提供した入力が安全かつ適切であるかを確認し、スワッププロセスに問題がないことを保証します。 これにより、不正確な入力によるトラブルやエラーを未然に防ぎます。

CallbackValidation 外部コントラクトとの相互作用における潜在的なリスクを軽減するための仕組みです。 トークンスワップのプロセス中には、他のコントラクトとの連携が必要な場合があります。 しかし、外部コントラクトとのやり取りはセキュリティの懸念が伴うことがあります。 CallbackValidationコントラクトは、外部コントラクトからのコールバック関数が適切であるかどうかを検証し、意図しないデータの流出や攻撃を防ぎます。 これにより、外部コントラクトとのやり取りにおけるセキュリティリスクを最小限に抑えることができます。

SwapRouterコントラクトは、手数料の計算や送金などの支払い処理も担当しています。 ユーザーと流動性プロバイダーの間でトークンを送受信する際に、適用される手数料を考慮しながら、PeripheryPaymentsWithFeeおよびPeripheryPaymentsコントラクトを使用します。

PeripheryPaymentsWithFee このコントラクトは、トークンの送受信に関連する手数料の計算と処理を行います。 ユーザーがトークンを送信する際には、そのトランザクションに対する手数料を計算し、適切な方法で取り扱います。 これにより、手数料をトランザクションに組み込んでスムーズにトークンを送信することができます。

PeripheryPayments このコントラクトもトークンの送受信に関連する支払い処理を担当します。 しかし、PeripheryPaymentsWithFeeとは異なり、手数料が含まれない場合や手数料が固定の場合など、シンプルな支払いシナリオに使用されます。

SwapRouterコントラクトは、これらの支払い処理コントラクトを利用して、ユーザーと流動性プロバイダーの間でトークンの送受信を円滑に行います。 手数料の計算や取り扱いは正確に行われ、トークンの送受信に伴う混乱やエラーを防ぎます。 これにより、ユーザーと流動性プロバイダーの間での取引が公平かつ透明に行われることを保証します。

さらに、ユーザーエクスペリエンスと効率を向上させるための追加機能もSwapRouterコントラクトに組み込まれています。 バッチ処理された関数呼び出しを可能にするMulticallコントラクトが含まれており、ガスコストを削減し、コントラクト間の相互作用を最適化します。 また、ユーザーが自分自身のアドレスからトークン転送の承認を直接行えるようにするSelfPermitコントラクトも統合されています。

Multicallコントラクト この機能は、複数の関数呼び出しを1つのトランザクションにまとめて実行することを可能にするものです。 通常、複数の関数を順番に呼び出す場合、それぞれの関数呼び出しにガスがかかります。 しかし、Multicallコントラクトを使用すると、1つのトランザクション内でまとめて関数を呼び出すことができるため、ガスコストを削減できます。 これにより、効率的なトランザクションの実行が可能になります。

SelfPermitコントラクト この機能は、ユーザーが自分自身のアドレスからトークンの転送を承認することを簡単にするものです。 通常、トークンの承認は別のトランザクションを介して行われますが、SelfPermitコントラクトを使用すると、ユーザーは直接自分のアドレスからトークンの転送を承認できます。 これにより、ユーザーエクスペリエンスが向上し、手続きが簡略化されます。

これらの追加機能は、ユーザーエクスペリエンスを向上させ、トランザクションの効率性を高めるためにSwapRouterコントラクトに取り入れられています。 バッチ処理による関数呼び出しの結合と、トークンの承認を自分自身のアドレスから簡単に行える仕組みが、より使いやすく効率的な取引を実現する一環として統合されています。

使い方

概要

SwapRouterコントラクトは、Uniswap V3分散型取引所上でのトークンスワップを支援するスマートコントラクトです。 このコントラクトは、ユーザーが複数の流動性プール間で最適なレートでトークンを交換できるようにするための一連の関数を提供します。

コントラクトの目標

SwapRouterコントラクトの目標は、複数のUniswap V3プールから流動性を集約し、効率的でコスト効果の高いトークンスワップを可能にすることです。 これにより、ユーザーはトークンスワップの際に最適なレートを得ることができ、スリッページ(価格変動による損失)やトランザクションコストを最小限に抑えることが狙いです。

使い方

1. SwapRouterコントラクトの初期化

SwapRouterコントラクトを使用するには、まずEthereumネットワーク上にデプロイする必要があります。 コントラクトのコンストラクタには以下の引数が必要です。

  • factory
    • Uniswap V3ファクトリーコントラクトのアドレス。
  • `weth
    • Wrapped Ether (WETH) コントラクトのアドレス。
  • `quoter
    • Quoterコントラクトのアドレス。
  • `refundee
    • 余剰のETHが返金されるアドレス。

2. トークンスワップの実行

SwapRouterコントラクトを使用してトークンスワップを実行するには、以下の手順に従います。

  • 入力トークンの数量をSwapRouterコントラクトが使用できるようにするため、入力トークンコントラクト上でapprove関数を呼び出して承認します。
  • 入力または出力トークンの数量を指定するために、exactInput関数またはexactOutput関数をSwapRouterコントラクト上で呼び出します。
    • どちらを使用するかは、入力または出力のトークン量を指定するかによります。
  • exactInput関数またはexactOutput関数に以下の引数を渡します。
    • SwapParameters
      • 入力および出力トークン、数量、および受取アドレスなどのスワップパラメータを含む構造体。
    • Path
      • スワップするトークンの経路を含む構造体で使用する流動性プールを指定します。
    • deadline
      • スワップの実行期限。
  • SwapRouterコントラクトは、トークンスワップを実行し、受取アドレスにスワップされたトークンの数量を返します。

3. 期待される出力量の取得

実際にスワップを実行せずにトークンスワップの期待される出力量を取得するには、SwapRouterコントラクト上でgetAmountsOut関数を呼び出します。 関数に以下の引数を渡します。

  • amountIn
    • 入力トークンの数量。
  • Path
    • スワップするトークンの経路を含む構造体で使用する流動性プールを指定します。 関数は期待される出力トークンの数量を返します。

4. 期待される入力量の取得

実際にスワップを実行せずにトークンスワップの期待される入力量を取得するには、SwapRouterコントラクト上でgetAmountsIn関数を呼び出します。 関数に以下の引数を渡します。

  • amountOut
    • 希望する出力トークンの数量。
  • Path
    • スワップするトークンの経路を含む構造体で使用する流動性プールを指定します。 関数は期待される入力トークンの数量を返します。

5. プールのアドレスの取得

指定したトークンペアに対するUniswap V3プールのアドレスを取得するには、SwapRouterコントラクト上でgetPool関数を呼び出します。 関数に以下の引数を渡します。

  • tokenA
    • ペアの最初のトークンのアドレス。
  • tokenB
    • ペアの2番目のトークンのアドレス。 関数は対応するUniswap V3プールのアドレスを返します。

イベント

SwapRouterコントラクトは、以下のイベントを発行します。

Swap

  • sender
    • スワップを開始したユーザーのアドレス。
  • SwapInfo
    • スワップに関する情報を含む構造体。
    • 入力および出力トークン、数量、受取アドレスなどが含まれます。

RefundETH

  • recipient
    • 余剰のETHが返金されたアドレス。
  • amount
    • 返金されたETHの数量。

RefundERC20

  • token
    • 返金されたERC20トークンのアドレス。
  • recipient
    • 余剰のトークンが返金されたアドレス。
  • amount
    • 返金されたトークンの数量。

ExactInput

  • SwapInfo
    • スワップに関する情報を含む構造体。
    • 入力および出力トークン、数量、受取アドレスなどが含まれます。

ExactOutput

  • SwapInfo
    • スワップに関する情報を含む構造体。
    • 入力および出力トークン、数量、受取アドレスなどが含まれます。

Multicall

  • data
    • マルチコールのデータ。

関連EIP/ERC

パラメータ

_factory

特定のプールをデプロイしたコントラクトのアドレスです。 具体的には、IUniswapV3Factoryというインターフェースに従て作成されたことを意味します。 このインターフェースは、Uniswap V3のプールを作成するために必要な機能とメソッドを定義しています。

_WETH9

Wrapped Ether(WETH) コントラクトのアドレスを指します。 WETHは、EthereumのネイティブトークンであるEther(ETH) をERC20トークンとしてラップしたものです。 ETHは通常、スマートコントラクトとやり取りする際に直接扱うことが難しいため、WETHとしてラップすることで取引やスマートコントラクト内での処理が容易になります。 _WETH9コントラクトのアドレスは、ETHをERC20トークンとして扱うためのインターフェースを提供し、ETHをトークンと同じように取引するための手段を提供します。

SwapRouter

変数

DEFAULT_AMOUNT_IN_CACHED

Exact Output Swapの計算と確認の過程で使用される変数であり、実際のスワップの処理と関連しています。

スワップの途中で次の2つのケースを考慮しています。

  1. Exact Input Swap(特定の入力量でスワップを実行する)
  2. Exact Output Swap(特定の出力量でスワップを実行する)

「DEFAULT_AMOUNT_IN_CACHED」は、2番目のケース、つまりExact Output Swapの処理に関連しています。 Exact Output Swapの場合、指定した出力量を得るために必要な入力量を計算します。 しかし、計算された入力量が厳密には得られない可能性があるため、その値を保持するために「DEFAULT_AMOUNT_IN_CACHED」が使用されます。

例えば、あるトークンAをトークンBにスワップする場合を考えます。 Exact Output Swapの目標は、指定した出力量のトークンBを得ることです。 しかし、スワップ時の市場の価格変動により、計算された入力量が厳密に一致しないことがあります。

このとき、「DEFAULT_AMOUNT_IN_CACHED」は計算された入力量を保持しており、その値に基づいてトランザクションが進行します。 その後、実際にスワップが実行され、どれだけの入力量が必要だったかが確定します。 この確定した値と指定した出力量を比較して、取引が適切に行われたかどうかを確認することができます。


amountInCached

計算された入力量を一時的に保持し、実際のスワップの結果と照らし合わせて取引の正確性を確認するために使われる変数。

この変数は特に、Exact Output Swa(特定の出力量を得るスワップ)の場面で使用されます。

以下の場合を考えます。

  • トークンAとトークンBのスワップを行う場合。
  • 特定の出力量である「10トークンB」を得たい。
  • 現在の市場価格により、この出力量を得るために必要な入力量は「15トークンA」。

このとき、Exact Output Swapを実行する際に「amountInCached」が使われます。

  1. 最初に、コントラクトは計算を行い、15トークンAをスワップに使う必要があることを計算します。
    • この計算された入力量が「amountInCached」に一時的に保存されます。
  2. スワップが実行され、実際のトランザクションが進行します。
    • この段階で、「amountInCached」に保存されている15トークンAを使用して、10トークンBを得るようなスワップが実行されます。
  3. スワップが完了すると、実際に得られたトークンBの量が確定します。
    • 例えば、実際には12トークンBが得られたとします。
  4. コントラクトは「amountInCached」に保存された15トークンAと、実際に得られた12トークンBを比較します。
    • この比較を通じて、スワップが正しく行われたかどうかを確認することができます。

構造体

SwapCallbackData

概要 SwapCallbackDataは、スワップのコールバック処理で使用されるデータを格納するための構造体。 主に2つのプロパティが含まれており、スワップの詳細情報を保持します。

パラメータ

  • path
    • このプロパティは、スワップの経路をバイト列として表現したもの。
    • スワップがどのトークンをどのトークンに対して行うかの情報をエンコードしています。
    • 具体的には、スワップするトークンのペアと関連する手数料情報が含まれています。
  • payer
    • スワップの支払い元となるアドレス。
    • スワップが実行される際に必要な支払いの発生元として使用されます。

用途 SwapCallbackData構造体は、スワップのコールバック処理内で、スワップのパスや支払いの情報を保持し、適切な処理を行うために使用されます。 スワップの成果を正確に取り扱い、必要なアクションを実行するために不可欠なデータ構造です。


関数

getPool

solidity
function getPool(
	address tokenA,
	address tokenB,
        uint24 fee
    ) private view returns (IUniswapV3Pool) {
        return IUniswapV3Pool(PoolAddress.computeAddress(factory, PoolAddress.getPoolKey(tokenA, tokenB, fee)));
}

概要 与えられたトークンペアと手数料(fee)に対するプールを取得する関数。 プールコントラクトが存在するかどうかは保証されません。

詳細 この関数は、Uniswap V3のプールのアドレスを計算し、そのアドレスを使用してプールコントラクトを取得します。 トークンA、トークンB、および手数料(fee)を指定して、プールコントラクトのアドレスを計算し、そのアドレスを使用してプールコントラクトを取得します。

引数

  • tokenA
    • スワップ対象のトークンAのアドレス。
  • tokenB
    • スワップ対象のトークンBのアドレス。
  • fee
    • スワップに適用される手数料。

戻り値

  • IUniswapV3Pool
    • 指定したトークンペアと手数料に対するプールコントラクトのインスタンスを返します。

uniswapV3SwapCallback

solidity
function uniswapV3SwapCallback(
	int256 amount0Delta,
	int256 amount1Delta,
	bytes calldata _data
	) external override {
	require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
	SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData));
	(address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();
	CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);

	(bool isExactInput, uint256 amountToPay) =
	    amount0Delta > 0
		? (tokenIn < tokenOut, uint256(amount0Delta))
		: (tokenOut < tokenIn, uint256(amount1Delta));
	if (isExactInput) {
	    pay(tokenIn, data.payer, msg.sender, amountToPay);
	} else {
	    // either initiate the next swap or pay
	    if (data.path.hasMultiplePools()) {
		data.path = data.path.skipToken();
		exactOutputInternal(amountToPay, msg.sender, 0, data);
	    } else {
		amountInCached = amountToPay;
		tokenIn = tokenOut; // swap in/out because exact output swaps are reversed
		pay(tokenIn, data.payer, msg.sender, amountToPay);
	    }
	}
}

概要 Uniswap V3のスワップコールバックを処理する関数。 スワップが行われた際に呼び出され、スワップの詳細を処理します。

詳細 この関数は、スワップが行われたときに呼び出され、トークンのスワップに関する情報を受け取ります。 スワップが完了したトークンの量や手数料などの情報を使用して、適切な処理を行います。

引数

  • amount0Delta
    • トークン0の量の変化量。
  • amount1Delta
    • トークン1の量の変化量。
  • calldata _data
    • スワップのコールバックデータ。

戻り値 この関数は戻り値がありません。

uniswapV3SwapCallbackは、Uniswap V3プロトコル内で発生するスワップの完了後に呼び出される関数です。 このコールバックは、スワップが実際に行われた後に、追加の処理やアクションを行うために使用されます。

Uniswap V3では、トークンのスワップはプール内で行われます。 スワップが行われたときには、その結果としてトークンの残高が変化し、プール内の価格も変動する可能性があります。 uniswapV3SwapCallbackは、このようなスワップの完了後に呼び出され、以下の目的で使用されます。

  1. スワップ結果の確認 スワップが成功したかどうか、実際に得られたトークンの量が期待通りかどうかを確認します。
  2. ログやイベントの記録 スワップに関連する情報を記録し、トランザクションの詳細をトラッキングします。
  3. リクエストの追加処理 スワップが完了した後に追加のアクションを行う場合、その処理を実行します。

このコールバックは、スワップが完了してから自動的に呼び出されるため、プロトコル内のスワップの追跡や付加的な処理を行うのに便利です。 このコントラクト内では、uniswapV3SwapCallbackを使用してスワップの結果を確認し、必要な追加処理を行うことが実装されています。


exactInputInternal

solidity
function exactInputInternal(
        uint256 amountIn,
        address recipient,
        uint160 sqrtPriceLimitX96,
        SwapCallbackData memory data
    ) private returns (uint256 amountOut) {
        // allow swapping to the router address with address 0
        if (recipient == address(0)) recipient = address(this);

        (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool();

        bool zeroForOne = tokenIn < tokenOut;

        (int256 amount0, int256 amount1) =
            getPool(tokenIn, tokenOut, fee).swap(
                recipient,
                zeroForOne,
                amountIn.toInt256(),
                sqrtPriceLimitX96 == 0
                    ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
                    : sqrtPriceLimitX96,
                abi.encode(data)
            );

        return uint256(-(zeroForOne ? amount1 : amount0));
}

概要 指定した入力量でスワップを行うための関数。

詳細 この関数は、Exact Input Swap(特定の入力量でスワップを行う)の処理を内部で行います。 トークンの入力量、受取アドレス、価格制限、およびスワップの詳細情報を使用してスワップを実行します。

引数

  • amountIn
    • スワップに使用する入力量。
  • recipient
    • トークンを受け取るアドレス。
  • sqrtPriceLimitX96
    • 価格制限。
  • SwapCallbackData
    • スワップのコールバックデータ。

戻り値

  • amountOut
    • スワップによって得られる出力量。

sqrtPriceLimitX96は、Uniswap V3プロトコル内で使用される価格制限のパラメータです。 このパラメータは、スワップが行われる際に設定され、スワップの価格を特定の範囲内に制限する役割を果たします。

Uniswap V3では、プール内の価格は連続的に変化します。 この価格は「square root of the price」(平方根価格)として表現され、その値はsqrtPriceX96として取得できます。 価格は連続的に変動するため、ある時点での価格を正確に予測することは難しいですが、sqrtPriceLimitX96を使用することで価格の変動を制限することができます。

具体的には、sqrtPriceLimitX96はスワップの価格制限を設定するためのパラメータであり、以下の役割を持ちます。

  • sqrtPriceLimitX96未満の価格でスワップを行うことが保証されます。
    • これにより、ある価格範囲内でのスワップを制御し、大きな価格変動を避けることができます。
  • スワップがsqrtPriceLimitX96を超えないように制限することで、プール内の価格をある範囲内に維持し、市場の安定性を保つ効果があります。

具体的な使用例を考えてみましょう。

  • スワップの価格変動が急激である場合、価格の大幅な変動を避けるために、特定のsqrtPriceLimitX96を設定してスワップを行います。
  • ボラティリティが低い場合、価格制限を設けずにスワップを行い、市場価格に近い価格で取引を行います。

このように、sqrtPriceLimitX96はスワップの価格を制限するための重要なパラメータであり、Uniswap V3プロトコルの機能を制御する際に使用されます。


exactInputSingle

solidity
function exactInputSingle(ExactInputSingleParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint256 amountOut)
    {
        amountOut = exactInputInternal(
            params.amountIn,
            params.recipient,
            params.sqrtPriceLimitX96,
            SwapCallbackData({path: abi.encodePacked(params.tokenIn, params.fee, params.tokenOut), payer: msg.sender})
        );
        require(amountOut >= params.amountOutMinimum, 'Too little received');
}

概要 単一のトランザクションで指定した入力量を使用してスワップを行うための関数。 スワップの価格制限を指定してスワップが行われ、結果として得られる出力量を計算します。

詳細 この関数は、指定された入力量を使用してスワップを行い、出力量を計算します。 スワップの価格制限を設定できるため、出力量が予測可能な範囲内に制限されます。

引数

  • ExactInputSingleParams
    • スワップのパラメータを格納した構造体。具体的な引数は以下になります。
    • amountIn
      • 入力量となるトークンの量。
    • recipient
      • スワップ結果の受取先アドレス。
    • sqrtPriceLimitX96
      • スワップの価格制限(平方根価格)。
    • tokenIn
      • 入力トークンのアドレス。
    • tokenOut
      • 出力トークンのアドレス。
    • fee
      • 手数料。
    • amountOutMinimum
      • 最小受取額。

戻り値

  • amountOut
    • スワップ結果として得られる出力トークンの量。

exactInputSingle関数は、1回のスワップを通じて指定された入力量を使用して出力量を計算し、その出力量を最小出力量と比較して確認するための関数です。

具体的な例をみていきましょう。

  • 入力トークン: トークンA
  • 出力トークン: トークンB
  • 手数料(fee): 0.3%
  • 入力量: 100 トークンA
  • 最小出力量: 200 トークンB
  • 価格制限(平方根価格): 1500000000000

この例では、exactInputSingle関数を使用して、100トークンAを入力して得られる最小出力量が200トークンB以上であるかを確認します。

  1. スワップの実行
  • ExactInputSingleParamsには、入力量として100トークンA、最小出力量として200トークンB、価格制限(平方根価格)を含めたパラメータが設定されます。
  • 関数は、指定した入力量を使用して出力量(トークンB)を計算します。
    • この計算は価格制限も考慮して行われます。
  • 仮に、このスワップによって得られる出力量が250トークンBであるとします。
  1. 最小出力量の確認
  • スワップが実行された後、出力量が250トークンBであることが分かります。
  • この出力量は最小出力量(200トークンB)を超えているため、条件を満たしています。

このように、exactInputSingle関数は、1回のスワップを通じて指定した入力量を使用して出力量を計算し、その出力量が最小出力量を満たしているかを確認するために使用されます。 出力量が最小出力量を超えている場合、スワップは実行されます。

exactInput

solidity
function exactInput(ExactInputParams memory params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint256 amountOut)
    {
        address payer = msg.sender; // msg.sender pays for the first hop

        while (true) {
            bool hasMultiplePools = params.path.hasMultiplePools();

            // the outputs of prior swaps become the inputs to subsequent ones
            params.amountIn = exactInputInternal(
                params.amountIn,
                hasMultiplePools ? address(this) : params.recipient, // for intermediate swaps, this contract custodies
                0,
                SwapCallbackData({
                    path: params.path.getFirstPool(), // only the first pool in the path is necessary
                    payer: payer
                })
            );

            // decide whether to continue or terminate
            if (hasMultiplePools) {
                payer = address(this); // at this point, the caller has paid
                params.path = params.path.skipToken();
            } else {
                amountOut = params.amountIn;
                break;
            }
        }

        require(amountOut >= params.amountOutMinimum, 'Too little received');
}

概要 複数のスワップを組み合わせて指定した入力量を使用してスワップを行うための関数。

詳細 この関数は、複数のスワップを組み合わせて指定した入力量を使用してスワップを行います。 複数のプールを経由する場合、連続してスワップが行われ、直前のスワップの出力量が次のスワップの入力量となります。

引数

  • ExactInputParams
    • スワップのパラメータを格納した構造体。
    • 具体的な引数は以下です。
    • amountIn
      • 入力量となるトークンの量。
    • recipient
      • スワップ結果の受取先アドレス。
    • sqrtPriceLimitX96
      • スワップの価格制限(平方根価格)。
    • path
      • スワップ経路を表すバイト列。
    • amountOutMinimum
      • 最小受取額。
    • deadline
      • トランザクションの締め切り時刻。

戻り値

  • amountOut
    • スワップ結果として得られる出力トークンの量。

複数のスワップを組み合わせて指定した入力量を使用してスワップを行うための関数です。 具体例を通じてこの関数の動作を詳しく説明します。

具体的な例でみていきます。

  • 入力トークン: トークンA
  • 出力トークン: トークンB
  • 手数料(fee): 0.3%
  • 入力量: 100 トークンA
  • 最小出力量: 200 トークンB
  • 価格制限(平方根価格): 1500000000000

この例では、トークンAをトークンBに変換したいと考えています。 exactInput関数を使用して、100トークンAを最小でも200トークンBに交換するプロセスを説明します。

  1. 初回のスワップ
  • ExactInputParamsには、入力量として100トークンA、最小出力量として200トークンB、価格制限(平方根価格)を含めたパラメータが設定されます。
  • 初回のスワップは、このパラメータをもとにトークンAをトークンBに交換します。
  • このスワップの結果、得られる出力量(トークンB)が250トークンBであると仮定します。
  1. 2回目のスワップ
  • 1回目のスワップの結果得られた250トークンBを次のスワップの入力量として使用します。
  • ExactInputParamsには、入力量として250トークンB、最小出力量として200トークンB、価格制限を含めたパラメータが設定されます。
  • 2回目のスワップは、このパラメータをもとにトークンBをトークンAに交換します。
  • このスワップの結果、得られる出力量(トークンA)が100トークンAであると仮定します。

このプロセスを通じて、最初に指定した入力量(100トークンA)を使用して、最終的に100トークンAを最小でも200トークンBに交換することができました。 このように、exactInput関数を使用して複数のスワップを組み合わせて効率的にトークンを交換することができます。

exactOutputInternal

solidity
function exactOutputInternal(
        uint256 amountOut,
        address recipient,
        uint160 sqrtPriceLimitX96,
        SwapCallbackData memory data
    ) private returns (uint256 amountIn) {
        // allow swapping to the router address with address 0
        if (recipient == address(0)) recipient = address(this);

        (address tokenOut, address tokenIn, uint24 fee) = data.path.decodeFirstPool();

        bool zeroForOne = tokenIn < tokenOut;

        (int256 amount0Delta, int256 amount1Delta) =
            getPool(tokenIn, tokenOut, fee).swap(
                recipient,
                zeroForOne,
                -amountOut.toInt256(),
                sqrtPriceLimitX96 == 0
                    ? (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1)
                    : sqrtPriceLimitX96,
                abi.encode(data)
            );

        uint256 amountOutReceived;
        (amountIn, amountOutReceived) = zeroForOne
            ? (uint256(amount0Delta), uint256(-amount1Delta))
            : (uint256(amount1Delta), uint256(-amount0Delta));
        // it's technically possible to not receive the full output amount,
        // so if no price limit has been specified, require this possibility away
        if (sqrtPriceLimitX96 == 0) require(amountOutReceived == amountOut);
}

概要 指定した出力量を得るためにスワップを行うための関数。指定した出力量に対して、必要な入力量を計算します。

詳細 この関数は、指定した出力量を得るためにスワップを行い、その結果として必要な入力量を計算します。 スワップの価格制限を指定してスワップが行われ、得られる入力量が計算されます。

引数

  • amountOut
    • 目標の出力トークンの量。
  • recipient
    • スワップ結果の受取先アドレス。
  • sqrtPriceLimitX96
    • スワップの価格制限(平方根価格)。
  • SwapCallbackData
    • スワップコールバックのデータ。

戻り値

  • amountIn
    • スワップを行うために必要な入力トークンの量。

exactOutputInternal関数は、指定された出力量を達成するために、逆方向のスワップを行うための関数です。 つまり、出力トークンの量を指定し、その量を達成するために入力トークンの量を計算します。

具体的な例でみていきます。

  • 入力トークン: トークンA
  • 出力トークン: トークンB
  • 手数料(fee): 0.3%
  • 出力量: 200 トークンB
  • 価格制限(平方根価格): 1500000000000

この例では、200トークンBを得るために必要な入力トークンの量を計算するために、exactOutputInternal関数を使用します。

  1. スワップの実行
  • exactOutputInternal関数に、出力量として200トークンB、受取アドレス、価格制限(平方根価格)を含めたパラメータを渡します。
  • 関数は、指定した出力量を達成するために必要な入力トークンの量を計算します。
  • この計算の結果、入力量が150トークンAであると仮定します。
  1. スワップ実行の結果
  • スワップが実行され、200トークンBが受取アドレスに送信されます。
  • このスワップによって消費される入力トークンの量は150トークンAです。

このように、exactOutputInternal関数を使用することで、指定した出力量を達成するために必要な入力トークンの量を計算し、その量でスワップを実行することができます。 この関数は、逆方向のスワップを行う際に便利です。


exactOutputSingle

solidity
function exactOutputSingle(ExactOutputSingleParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint256 amountIn)
    {
        // avoid an SLOAD by using the swap return data
        amountIn = exactOutputInternal(
            params.amountOut,
            params.recipient,
            params.sqrtPriceLimitX96,
            SwapCallbackData({path: abi.encodePacked(params.tokenOut, params.fee, params.tokenIn), payer: msg.sender})
        );

        require(amountIn <= params.amountInMaximum, 'Too much requested');
        // has to be reset even though we don't use it in the single hop case
        amountInCached = DEFAULT_AMOUNT_IN_CACHED;
}

概要 指定されたアウトプット量を得るために単一のスワップを実行するための関数。

詳細 この関数は、与えられたパラメータに基づいて、アウトプット量を得るためにスワップを実行します。 具体的には、与えられたアウトプット量を達成するために必要なインプット量を計算し、スワップを行います。

引数

  • params
    • スワップのパラメータを含む構造体。
    • 具体的な引数は以下です。
    • amountOut
      • 欲しいアウトプット量。
    • recipient
      • アウトプットを受け取るアドレス。
    • sqrtPriceLimitX96
      • スワップの価格制限。
    • tokenOut
      • アウトプットトークンのアドレス。
    • fee
      • スワップの手数料。
    • tokenIn
      • インプットトークンのアドレス。
    • amountInMaximum
      • 許容される最大インプット量。
    • deadline
      • スワップの締め切り時刻。

戻り値

  • amountIn
    • スワップに必要なインプット量。

exactOutputSingle 関数の具体例を用いて説明します。

想定するシナリオ。

  • アウトプットトークンとしてトークンAを指定し、アウトプット量として100トークンAを得たいとします。
  • このとき、インプットトークンとしてトークンBを使用します。
  • スワップ手数料は0.3%(3000) とし、スワップの締め切り時刻は明日の日付とします。

具体例のパラメータ。

  • amountOut: 100 トークンA
  • recipient: 0xAddress(アウトプットを受け取るアドレス)
  • sqrtPriceLimitX96: 0(価格制限はなし)
  • tokenOut: トークンAのアドレス
  • fee: 3000
  • tokenIn: トークンBのアドレス
  • amountInMaximum: 1000 トークンB
  • deadline: 明日の日付(unixタイムスタンプ)
solidity
ExactOutputSingleParams memory params = ExactOutputSingleParams({
    tokenOut: address(tokenA),
    tokenIn: address(tokenB),
    fee: 3000,
    recipient: 0xAddress,
    amountOut: 100, // 100 トークンAを得る
    amountInMaximum: 1000, // 最大 1000 トークンBまで許容
    sqrtPriceLimitX96: 0, // 価格制限なし
    deadline: tomorrowTimestamp // 明日の日付
});

uint256 amountIn = swapRouter.exactOutputSingle(params);

この例では、指定されたアウトプット量(100トークンA)を得るために、最大で1000トークンBまでのインプットを許容し、スワップを実行しています。 スワップを実行する際には、スワップの手数料や価格制限なども考慮され、必要なインプット量が計算されています。 スワップが成功した場合、amountIn には実際のインプット量が返されます。

exactOutput

solidity
function exactOutput(ExactOutputParams calldata params)
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint256 amountIn)
    {
        // it's okay that the payer is fixed to msg.sender here, as they're only paying for the "final" exact output
        // swap, which happens first, and subsequent swaps are paid for within nested callback frames
        exactOutputInternal(
            params.amountOut,
            params.recipient,
            0,
            SwapCallbackData({path: params.path, payer: msg.sender})
        );

        amountIn = amountInCached;
        require(amountIn <= params.amountInMaximum, 'Too much requested');
        amountInCached = DEFAULT_AMOUNT_IN_CACHED;
}

概要 指定されたアウトプット量を得るために複数のスワップを連続して実行するための関数。

詳細 この関数は、複数のスワップを連続して実行して指定されたアウトプット量を得ます。 中間のスワップではこのコントラクトがカストディを持ち、最終的なスワップではアウトプットを受け取るアドレスが支払いを行います。

引数

  • params
    • スワップのパラメータを含む構造体。
    • 具体的な引数は以下です。
    • path
      • スワップパスをエンコードしたバイト列。
    • amountOut
      • 欲しいアウトプット量。
    • recipient
      • アウトプットを受け取るアドレス。
    • deadline
      • スワップの締め切り時刻。

戻り値

  • amountIn
    • スワップに必要なインプット量。

具体例を用いて説明します。

想定するシナリオ。

  • アウトプットトークンとしてトークンAを指定し、アウトプット量として200トークンAを得たいとします。
  • このとき、インプットトークンとしてトークンBを使用します。
  • スワップ手数料は0.3%(3000) とし、スワップの締め切り時刻は明日の日付とします。

具体例のパラメータ。

  • amountOut200 トークンA
  • recipient: 0xAddress(アウトプットを受け取るアドレス)
  • path: トークンB → トークンA
  • amountInMaximum: 1000 トークンB
  • deadline: 明日の日付(unixタイムスタンプ)
solidity
ExactOutputParams memory params = ExactOutputParams({
    path: abi.encodePacked(address(tokenB), 3000, address(tokenA)), // トークンB → トークンA
    recipient: 0xAddress,
    amountOut: 200, // 200 トークンAを得る
    amountInMaximum: 1000, // 最大 1000 トークンBまで許容
    deadline: tomorrowTimestamp // 明日の日付
});

uint256 amountIn = swapRouter.exactOutput(params);

この例では、指定されたアウトプット量(200トークンA)を得るために、最大で1000トークンBまでのインプットを許容し、スワップを実行しています。 スワップを実行する際には、スワップの手数料や価格制限なども考慮され、必要なインプット量が計算されています。 スワップが成功した場合、amountIn には実際のインプット量が返されます。

PeripheryValidation

PeripheryValidation
solidity
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;

import './BlockTimestamp.sol';

abstract contract PeripheryValidation is BlockTimestamp {
    modifier checkDeadline(uint256 deadline) {
        require(_blockTimestamp() <= deadline, 'Transaction too old');
        _;
    }
}

修飾子

checkDeadline

指定された締め切り時間deadlineを検証するために使用されます。 トランザクションを実行する前に、ブロックのタイムスタンプを取得して現在の時間と比較し、締め切り時間を過ぎていないことを確認します。 もし締め切り時間を過ぎている場合、'Transaction too old' というエラーメッセージとともにトランザクションが失敗します。

PeripheryPaymentsWithFee

関数

PeripheryPaymentsWithFee

solidity
function unwrapWETH9WithFee(
        uint256 amountMinimum,
        address recipient,
        uint256 feeBips,
        address feeRecipient
    ) public payable override {
        require(feeBips > 0 && feeBips <= 100);

        uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this));
        require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9');

        if (balanceWETH9 > 0) {
            IWETH9(WETH9).withdraw(balanceWETH9);
            uint256 feeAmount = balanceWETH9.mul(feeBips) / 10_000;
            if (feeAmount > 0) TransferHelper.safeTransferETH(feeRecipient, feeAmount);
            TransferHelper.safeTransferETH(recipient, balanceWETH9 - feeAmount);
        }
}

概要 指定された量の WETH9(Wrapped Ether) トークンをETHに変換し、手数料を差し引いて受取アドレスに送金する関数。

詳細 この関数は、WETH9トークンをETHに変換して手数料を差し引いた後、指定された受取アドレスと手数料受取アドレスに対してETHを送金します。 手数料は指定された割合で差し引かれ、残りのETHが受取アドレスに送金されます。

引数

  • amountMinimum
    • 変換する最小のWETH9量。
    • WETH9の残高がこれより少ない場合、関数は失敗します。
  • recipient
    • ETHを受け取るアドレス。
  • feeBips
    • 手数料の割合を表す基準。
    • 0から100までの値を取ります。
  • feeRecipient
    • 手数料を受け取るアドレス。

戻り値 戻り値はない。

具体例: 以下の条件を持つ状況を考えます。

  • 送金するトークンのアドレス: 0x123...
  • 最小送金数量: 200トークン
  • 送金先アドレス: 0x456...
  • 手数料の割合: 2% (200ビップス)
  • 手数料の受取先アドレス: 0x789...

この場合、関数の呼び出しは以下のようになります。

solidity
sweepTokenWithFee(0x123..., 200, 0x456..., 200, 0x789...);

関数の処理のステップを説明します。

  1. まず、feeBipsが200であり、2%の手数料が設定されているため、手数料が計算されます。
  • balanceTokenがトークンの残高で、例えば1000トークンとします。
  • feeAmountは1000 * 200 / 10000 = 20と計算されます。
    • つまり、20トークンが手数料として差し引かれます。
  1. その後、amountMinimumが200であり、残高が最小送金数量を満たすかどうかチェックします。
  • 残高が1000トークンなので条件を満たします。
  1. 残高が手数料を差し引いた残りのトークン量となります。
  • つまり、980トークン (1000 トークン - 20 トークン) が送金されることになります。
  1. 最終的に、手数料と残りのトークンがそれぞれfeeRecipientとrecipientに送金されます。

sweepTokenWithFee

solidity
function sweepTokenWithFee(
        address token,
        uint256 amountMinimum,
        address recipient,
        uint256 feeBips,
        address feeRecipient
    ) public payable override {
        require(feeBips > 0 && feeBips <= 100);

        uint256 balanceToken = IERC20(token).balanceOf(address(this));
        require(balanceToken >= amountMinimum, 'Insufficient token');

        if (balanceToken > 0) {
            uint256 feeAmount = balanceToken.mul(feeBips) / 10_000;
            if (feeAmount > 0) TransferHelper.safeTransfer(token, feeRecipient, feeAmount);
            TransferHelper.safeTransfer(token, recipient, balanceToken - feeAmount);
        }
}

概要 指定されたトークンをトークン数量と手数料を考慮して送金する関数。

詳細 この関数は、指定されたトークンを受取アドレスと手数料受取アドレスに送金します。 送金されるトークン数量は、手数料が差し引かれた残りの数量となります。

引数

  • token
    • 送金するトークンのアドレス。
  • amountMinimum
    • 送金する最小のトークン数量。
    • 残高がこれより少ない数量の場合、関数は失敗します。
  • recipient(address)
    • トークンを受け取るアドレス。
  • feeBips
    • 手数料の割合を表す基準。
    • 0から100までの値を取ります。
  • feeRecipient
    • 手数料を受け取るアドレス。

戻り値 戻り値はなし。

具体例 以下の状況を考えます。

  • 送金するトークンのアドレス: 0xabc...
  • 最小送金数量: 500トークン
  • 送金先アドレス: 0xdef...
  • 手数料の割合: 1%(100ビップス)
  • 手数料の受取先アドレス: 0xghi...

関数を呼び出す例を示します。

solidity
sweepTokenWithFee(0xabc..., 500, 0xdef..., 100, 0xghi...);

関数の処理のステップを説明します。

  1. feeBipsが100であり、1%の手数料が設定されているため、手数料を計算します。
  • balanceTokenはこのコントラクトの残高で、例えば1000トークンとします。
  • feeAmountは1000 * 100 / 10000 = 10と計算されます。
    • つまり、10トークンが手数料として差し引かれます。
  1. amountMinimumが500であり、残高がこれを満たしているかチェックされます。 残高が1000トークンなので条件を満たします。
  2. 手数料を差し引いた残りのトークン量が計算されます。 つまり、990トークン (1000 トークン - 10 トークン) が送金されることになります。
  3. 手数料の受取先アドレスである 0xghi... に10トークンが送金されます。
  4. 送金先アドレスである 0xdef... に残りのトークンである990トークンが送金されます。

このように、関数 sweepTokenWithFee は指定したトークンを手数料を差し引いた形で指定のアドレスに送金するための機能を提供します。 手数料は送金するトークンの一部を表し、手数料の受取先アドレスに送金されます。 残りのトークンは指定した送金先アドレスに送金されます。

Multicall

関数

multicall

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

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

            results[i] = result;
        }
}

概要 複数の関数コールを一括で実行するための関数。 指定されたデータの配列を使って、delegatecallによって複数の関数を順番に実行し、結果をまとめて返します。

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

引数:

  • data
    • デリゲートコールで実行する関数のデータが格納された配列。
    • 各要素はバイト列で、それぞれの関数呼び出しに対するデータが含まれています。

戻り値:

  • results
    • 各関数呼び出しの結果が格納されたバイト列の配列。
    • 各要素は、対応する関数呼び出しの結果が格納されています。

処理

  1. results 配列を指定された data 配列の長さに合わせて初期化します。
  2. ループを使って、各関数呼び出しのデータを順番に処理します。i はループのインデックスです。
  3. delegatecallを使用して、data[i]に格納された関数をデリゲートコールします。delegatecallは、このコントラクトのコードを実行するため、外部のコントラクトやライブラリ内の関数を呼び出すことができます。
  4. success変数にデリゲートコールの結果が格納され、関数の実行が成功したかどうかが判断されます。
  5. もし関数の実行が失敗した場合、エラーハンドリングが行われます。 これは失敗した関数のエラーメッセージを取得して再度エラーを投げるプロセスです。
  6. 成功した場合、results[i] に関数の結果が格納されます。

BlockTimestamp

関数

_blockTimestamp

solidity
function _blockTimestamp() internal view virtual returns (uint256) {
        return block.timestamp;
}

概要 ブロックのタイムスタンプを返す関数。

詳細 この関数は、テスト目的で使用されることを目的としています。 テストコード内でこの関数をオーバーライドすることで、任意のブロックタイムスタンプをシミュレートできます。 一般的な実装としては、現在のブロックのタイムスタンプを返すために使用されます。

戻り値 ブロックのタイムスタンプを表す符号なし整数型(uint256)の値。

SelfPermit

関数

selfPermit

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

概要 トークンの所有者が自身に対して許可を与えるためのERC20パーミットを呼び出す関数。

詳細 トークンの所有者は、この関数を使用して自身のアドレスに対して一定数量のトークンをスマートコントラクトに対して操作を許可します。 これにより、スマートコントラクトが所有者の代わりにトークンを操作できるようになります。

引数:

  • token
    • 対象のトークンのアドレス。
  • value
    • 許可するトークンの数量。
  • deadline
    • パーミットの期限。
  • v
    • ECDSA署名のvパラメータ。
  • r
    • ECDSA署名のrパラメータ。
  • s
    • ECDSA署名のsパラメータ。

selfPermitIfNecessary

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

概要 トークンの所有者がトークンの許可額をチェックし、必要な場合に許可を実行する関数。

詳細 トークンの所有者は、この関数を使用して、スマートコントラクトが所定のトークン額を操作できるかどうかを確認します。 もし許可額が不足している場合、selfPermit関数が呼び出されて許可が行われます。

引数:

  • token
    • 対象のトークンのアドレス。
  • value
    • 必要なトークンの数量。
  • deadline
    • パーミットの期限。
  • v
    • ECDSA署名のvパラメータ。
  • r
    • ECDSA署名のrパラメータ。
  • s
    • ECDSA署名のsパラメータ。

selfPermitAllowed

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

概要 トークンの所有者が自身に対してpermitAllowedを呼び出す関数。

詳細 トークンの所有者は、この関数を使用して自身のアドレスに対して一定数量のトークンをスマートコントラクトに対して操作を許可します。 この関数は、トークンが使用するEIP2612のpermitAllowed関数を呼び出します。

引数:

  • token
    • 対象のトークンのアドレス。
  • nonce
    • パーミットのnonce。
  • expiry
    • パーミットの有効期限。
  • v
    • ECDSA署名のvパラメータ。
  • r
    • ECDSA署名のrパラメータ。
  • s
    • ECDSA署名のsパラメータ。

selfPermitAllowedIfNecessary

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

概要: この関数は、トークンの所有者がpermitAllowedの許可額をチェックし、必要な場合に自己許可を実行するためのメソッドです。

詳細: トークンの所有者は、この関数を使用して、スマートコントラクトが所定のトークン額を操作できるかどうかを確認します。もし許可額が不足している場合、selfPermitAllowed関数が呼び出されて自己許可が行われます。この関数は、トークンが使用するEIP-2612のpermitAllowed関数を呼び出します。

引数:

  • token:対象のトークンのアドレス。
  • nonce:パーミットのnonce。
  • expiry:パーミットの有効期限。
  • v:ECDSA署名のvパラメータ。
  • r:ECDSA署名のrパラメータ。
  • s:ECDSA署名のsパラメータ。

戻り値: この関数は戻り値を持ちません。

PeripheryPayments

関数

unwrapWETH9

solidity
function unwrapWETH9(uint256 amountMinimum, address recipient) external payable override {
        uint256 balanceWETH9 = IWETH9(WETH9).balanceOf(address(this));
        require(balanceWETH9 >= amountMinimum, 'Insufficient WETH9');

        if (balanceWETH9 > 0) {
            IWETH9(WETH9).withdraw(balanceWETH9);
            TransferHelper.safeTransferETH(recipient, balanceWETH9);
        }
}

概要 WETH9(Wrapped Ether) をETH(Ether) に戻す関数。 指定された最小数量のWETH9を受け取り、それに対応するETHを指定された受取アドレスに送信します。

引数

  • amountMinimum
    • 最小のWETH9数量。
    • WETH9の残高がこれより少ない場合関数は失敗します。
  • recipient
    • ETHを受け取るアドレス。

sweepToken

solidity
function sweepToken(
        address token,
        uint256 amountMinimum,
        address recipient
    ) external payable override {
        uint256 balanceToken = IERC20(token).balanceOf(address(this));
        require(balanceToken >= amountMinimum, 'Insufficient token');

        if (balanceToken > 0) {
            TransferHelper.safeTransfer(token, recipient, balanceToken);
        }
}

概要 指定されたトークンをトークンの所有者(コントラクト自体)から指定された受取アドレスに送金する関数。

引数

  • token
    • 送金するトークンのアドレス。
  • amountMinimum
    • 最小のトークン数量。
    • トークンの残高がこれより少ない場合関数は失敗します。
  • recipient
    • トークンを受け取るアドレス。

refundETH

solidity
function refundETH() external payable override {
        if (address(this).balance > 0) TransferHelper.safeTransferETH(msg.sender, address(this).balance);
}

概要 コントラクトに残っているETHをコントラクトの所有者(メッセージの送信者)に返金する関数。

pay

solidity
function pay(
        address token,
        address payer,
        address recipient,
        uint256 value
    ) internal {
        if (token == WETH9 && address(this).balance >= value) {
            // pay with WETH9
            IWETH9(WETH9).deposit{value: value}(); // wrap only what is needed to pay
            IWETH9(WETH9).transfer(recipient, value);
        } else if (payer == address(this)) {
            // pay with tokens already in the contract (for the exact input multihop case)
            TransferHelper.safeTransfer(token, recipient, value);
        } else {
            // pull payment
            TransferHelper.safeTransferFrom(token, payer, recipient, value);
        }
}

概要 支払いを行う関数。 WETH9を使用して支払うか、既にコントラクト内にあるトークンを使用して支払うか、またはトークンを引き落として支払うかの条件に基づいて、支払いを処理します。

引数

  • token
    • 支払うトークンのアドレス。
  • payer
    • 支払いを行うエンティティのアドレス。
  • recipient
    • 支払いを受け取るエンティティのアドレス。
  • value
    • 支払う数量。

イベント

なし

コード

コントラクト

  • contractsディレクトリ

インターフェース

  • interfacesディレクトリ

ベース

  • baseディレクトリ

ライブラリ

  • librariesディレクトリ

Uniswap Core コントラクト

  • @uniswapディレクトリ

Openzeppelin コントラクト

  • @openzeppelinディレクトリ

最後に

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

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