🦄

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

2023/08/26に公開

はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

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

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

https://www.bunzz.dev/decipher

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

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

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

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

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

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

概要

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

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

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

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

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

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

使い方

概要

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

https://chaldene.net/erc20

パラメータ

_factory

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

_WETH9

Wrapped Ether(WETH) コントラクトのアドレスを指します。
WETHは、EthereumのネイティブトークンであるEther(ETH)ERC20トークンとしてラップしたものです。
ETHは通常、スマートコントラクトとやり取りする際に直接扱うことが難しいため、WETHとしてラップすることで取引やスマートコントラクト内での処理が容易になります。
_WETH9コントラクトのアドレスは、ETHERC20トークンとして扱うためのインターフェースを提供し、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」が使用されます。


amountInCached

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

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


構造体

SwapCallbackData

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

パラメータ

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

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


関数

getPool

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

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
    • スワップのコールバックデータ。

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


exactInputInternal

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
    • スワップによって得られる出力量。

exactInputSingle

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
    • スワップ結果として得られる出力トークンの量。

exactInput

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
    • スワップ結果として得られる出力トークンの量。

exactOutputInternal

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
    • スワップを行うために必要な入力トークンの量。

exactOutputSingle

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
    • スワップに必要なインプット量。

exactOutput

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
    • スワップに必要なインプット量。

PeripheryValidation

PeripheryValidation
// 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

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
    • 手数料を受け取るアドレス。

戻り値
戻り値はない。


sweepTokenWithFee

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
    • 手数料を受け取るアドレス。

戻り値
戻り値はなし。

Multicall

関数

multicall

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によって複数の関数を順番に実行し、結果をまとめて返します。

https://chaldene.net/solidity-delegatecall

引数:

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

戻り値:

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

BlockTimestamp

関数

_blockTimestamp

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

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

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

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

SelfPermit

関数

selfPermit

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

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

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を呼び出す関数。

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

引数:

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

selfPermitAllowedIfNecessary

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

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

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

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

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

pay

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に関する記事を挙げているので、よければ見ていってください!

https://chaldene.net/

https://qiita.com/cardene

DeCipher |"Read me" for All of Contracts

Discussion