🦄

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

2023/08/29に公開

はじめに

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

https://cryptogames.co.jp/

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

https://cryptospells.jp/

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

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

https://www.bunzz.dev/decipher

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

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

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

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

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

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

概要

UniversalRouterコントラクトは、私たちのdAppエコシステムにおける重要な要素です。
このコントラクトは、プラットフォーム内で発生するさまざまなトランザクションの中枢として機能します。
具体的には、スワップ、支払い、報酬の収集など、異なるモジュールや外部プロトコルを横断する多様なトランザクションをスムーズに処理する役割を果たします。

目的

ユーザーに統一されたエクスペリエンスを提供することです。
ユーザーは様々なプロトコルやモジュールと簡単にやりとりでき、トークンのスワップ、支払い、報酬の収集などの複雑なアクションを円滑かつ効率的に実行できます。

責任

トランザクションのルーティング

異なるタイプのトランザクションを対応するモジュールやプロトコルに効率的に接続します。
特定のモジュールやプロトコルに関係なく、すべてのトランザクションが正確に安全に実行されることを確認します。

インターオペラビリティ

さまざまなモジュールやプロトコル間の相互運用性を促進することで、ユーザーが技術的な詳細を理解する必要なく、dAppエコシステムの異なるコンポーネントと簡単に対話できるようにします。

トランザクションの検証

トランザクションが必要な条件を満たしているかを確認し、トークンの残高、資金の適切な確認、ユーザーの許可などを検証します。
これにより、不正なトランザクションが実行されるのを防ぎます。

イベントの記録

重要なイベントやトランザクションの詳細を記録し、透明性と監査可能性を確保します。
ユーザーや開発者はトランザクションの実行状況を確認し、エコシステムの信頼性を保つためにこれらの情報を使用できます。

エラーハンドリング

トランザクションの実行中に問題が発生した場合、エラーを適切に処理してユーザーに適切なフィードバックを提供します。
これにより、エクスペリエンスの中断や資金の損失を最小限に抑えます。

UniversalRouterコントラクトは私たちのdAppエコシステム内でトランザクションを円滑かつ安全に実行するための重要な要素です。
ユーザーはさまざまなモジュールやプロトコルと対話することができ、同時に効率性、相互運用性、セキュリティを確保します。

使い方

UniversalRouterコントラクトは、異なるプロトコルでさまざまな種類のトランザクションを実行するためのユニバーサルなルーターとして機能するスマートコントラクトです。
このコントラクトは、異なるプロトコルとのやりとりに単一のインターフェースを提供し、指定されたプロトコルに基づいてトランザクションのルーティングを処理します。

コントラクトの目的

UniversalRouterコントラクトの目的は、異なるプロトコルでトランザクションを実行するための統一されたインターフェースを提供することです。
このコントラクトを使用することで、開発者は単一のコントラクトを介して複数のプロトコルとやりとりできるため、異なるプロトコルと個別にやりとりする複雑さを軽減し、統合プロセスを簡素化できます。

利用方法

コントラクトの目標を達成するために、以下のステップを実行します。

  1. UniversalRouterコントラクトをデプロイします。
  2. コントラクトを必要なパラメータで初期化します。
  3. ルート関数を呼び出して異なるプロトコル上でトランザクションを実行します。

UniversalRouterコントラクトのデプロイ

UniversalRouterコントラクトをデプロイするには、次の手順に従います。

  1. UniversalRouter.solコントラクトをコンパイルします。
  2. コンパイルされたコントラクトを希望するブロックチェーンネットワークにデプロイします。

コントラクトの初期化

UniversalRouterコントラクトは、次のパラメータで初期化できます。

  • dispatcher
    • Dispatcherコントラクトのアドレス。
  • rewardsCollector
    • RewardsCollectorコントラクトのアドレス。
  • routerImmutables
    • RouterImmutablesコントラクトのアドレス。

トランザクションの実行

異なるプロトコルでトランザクションを実行するには、route関数を呼び出します。
route関数は次のパラメータを受け取ります。

  • command
    • 指定されたプロトコルで実行されるコマンド。
  • data
    • コマンドの実行に必要なデータ。
  • path
    • コマンドを実行するために使用するプロトコルのパス。

route関数は以下のステップを実行します。

  1. コマンドとデータを検証します。
  2. 指定されたパスに基づいてプロトコルの実装アドレスを取得します。
  3. プロトコルの実装コントラクトのexecute関数をコマンドとデータと共に呼び出します。

関数

UniversalRouterコントラクトは以下の関数を提供します。

  • constructor
    • 指定されたパラメータでコントラクトを初期化します。
  • route
    • 指定されたコマンド、データ、およびパスに基づいて、異なるプロトコル上でトランザクションを実行します。

イベント

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

  • RouteExecuted
    • トランザクションがプロトコル上で正常に実行されたときに発行されます。
    • 以下の情報を含みます。
      • protocol
        • トランザクションが実行されたプロトコル。
      • command
        • 実行されたコマンド。
      • data
        • 実行されたデータ。

関連EIP/ERC

  • ERC721
  • ERC1155
  • ERC20
  • IERC721Receiver
  • IERC1155Receiver
  • IERC20
  • IUniswapV2Pair
  • IUniswapV3Pool
  • IUniswapV3SwapCallback
  • IERC165
  • IAllowanceTransfer
  • ICryptoPunksMarket
  • IWETH9

参考

  • UniversalRouter.sol
  • Dispatcher.sol
  • RewardsCollector.sol
  • RouterImmutables.sol
  • Commands.sol
  • IUniversalRouter.sol
  • V2SwapRouter.sol
  • V3SwapRouter.sol
  • BytesLib.sol
  • Payments.sol
  • Callbacks.sol
  • LockAndMsgSender.sol
  • ERC721.sol
  • ERC1155.sol
  • ERC20.sol
  • IAllowanceTransfer.sol
  • ICryptoPunksMarket.sol
  • SafeTransferLib.sol
  • IRewardsCollector.sol
  • IWETH9.sol
  • IERC721Receiver.sol
  • IERC1155Receiver.sol
  • IUniswapV2Pair.sol
  • UniswapV2Library.sol
  • Permit2Payments.sol
  • Constants.sol
  • V3Path.sol
  • SafeCast.sol
  • IUniswapV3Pool.sol
  • IUniswapV3SwapCallback.sol
  • IERC165.sol
  • IERC20.sol
  • SafeCast160.sol
  • IUniswapV3PoolImmutables.sol
  • IUniswapV3PoolState.sol
  • IUniswapV3PoolDerivedState.sol
  • IUniswapV3PoolActions.sol
  • IUniswapV3PoolOwnerActions.sol
  • IUniswapV3PoolEvents.sol

パラメータ

以下は、各項目をリストではなくh3の見出しで示したものです。

permit2

permit2コントラクトのアドレス。

weth9

weth9コントラクトのアドレス。

seaportV1_5

seaportV1_5コントラクトのアドレス。

seaportV1_4

seaportV1_4コントラクトのアドレス。

openseaConduit

openseaConduitコントラクトのアドレス。

nftxZap

nftxZapコントラクトのアドレス。

x2y2

x2y2コントラクトのアドレス。

foundation

foundationコントラクトのアドレス。

sudoswap

sudoswapコントラクトのアドレス。

elementMarket

elementMarketコントラクトのアドレス。

nft20Zap

nft20Zapコントラクトのアドレス。

cryptopunks

cryptopunksコントラクトのアドレス。

looksRareV2

looksRareV2コントラクトのアドレス。

routerRewardsDistributor

routerRewardsDistributorコントラクトのアドレス。

looksRareRewardsDistributor

looksRareRewardsDistributorコントラクトのアドレス。

looksRareToken

looksRareTokenコントラクトのアドレス。

v2Factory

v2Factoryコントラクトのアドレス。

v3Factory

v3Factoryコントラクトのアドレス。

pairInitCodeHash

ペア初期化コードのハッシュコード。

poolInitCodeHash

プール初期化コードのハッシュコード。

コントラクト

UniversalRouter

RouterImmutables

概要
RouterImmutablesはコントラクトの初期化パラメータを保持するためのコントラクト。

詳細
RouterImmutablesUniversalRouterコントラクトの初期化時に使用されます。
コントラクトのパラメータを設定し、後続のトランザクション実行においてこれらのパラメータを参照する役割を果たします。

引数

  • params
    • RouterParameters構造体型の初期化パラメータです。
    • これによってコントラクトの設定が行われます。

execute

execute
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline)
        external
        payable
        checkDeadline(deadline)
    {
        execute(commands, inputs);
}

概要
トランザクションを実行する関数。
指定されたコマンドと入力に基づいてトランザクションを実行します。

詳細
IUniversalRouterインターフェースに準拠し、指定されたコマンドと入力を使用してトランザクションを実行します。
また、指定された締め切り時間を超えて実行しようとすると、エラーが発生します。

引数

  • commands
    • 実行するコマンドのバイトデータ配列。
  • inputs
    • 各コマンドに対する入力のバイトデータ配列。
  • deadline
    • トランザクションの締め切り時間を表すタイムスタンプ。

戻り値
なし


execute (override)

execute
function execute(bytes calldata commands, bytes[] calldata inputs) public payable override isNotLocked {
        bool success;
        bytes memory output;
        uint256 numCommands = commands.length;
        if (inputs.length != numCommands) revert LengthMismatch();

        // loop through all given commands, execute them and pass along outputs as defined
        for (uint256 commandIndex = 0; commandIndex < numCommands;) {
            bytes1 command = commands[commandIndex];

            bytes calldata input = inputs[commandIndex];

            (success, output) = dispatch(command, input);

            if (!success && successRequired(command)) {
                revert ExecutionFailed({commandIndex: commandIndex, message: output});
            }

            unchecked {
                commandIndex++;
            }
        }
}

概要
execute関数のオーバーロードで、デッドラインのチェックを行わないバージョン。

詳細
execute関数のオーバーロードであり、締め切り時間のチェックを行わないバージョンです。
内部で同じ名前のexecute関数を呼び出してトランザクションを実行します。

引数

  • commands
    • 実行するコマンドのバイトデータ配列。
  • inputs
    • 各コマンドに対する入力のバイトデータ配列。

戻り値
なし


successRequired

successRequired
function successRequired(bytes1 command) internal pure returns (bool) {
        return command & Commands.FLAG_ALLOW_REVERT == 0;
}

概要
特定のコマンドに対して実行の結果が成功である必要があるかどうかを判定する関数。

詳細
コマンドがFLAG_ALLOW_REVERTを含まない場合は成功する必要があります。

引数

  • command
    • 判定対象のコマンドバイトデータです。

戻り値

  • bool
    • 成功が必要かどうかを示す真偽値です。

receive

receive
receive() external payable {}

概要
WETHやNFTプロトコルからETHを受け取るための関数。

詳細
外部からETHを受け取る際に呼び出されます。
WETHやNFTプロトコルからのETH受け取りに使用されます。

Dispatcher

複数の異なるコマンドを実行し、その結果を扱うためのコントラクト。
異なるコマンドのタイプに応じて、該当する関数を呼び出し、必要な引数やデータを処理します。
複数の異なるスマートコントラクトとやりとりするための中継役を果たします。
コマンドをデコードし、それぞれのコマンドタイプに対応する関数を呼び出すことで、異なる操作を実現しています。

パラメータ

  • commands
    • 実行するコマンドのバイト列。
    • 各コマンドは1バイトで表されます。
  • inputs
    • 各コマンドに対する入力データのバイト列の配列。

戻り値
なし


dispatch

dispatch
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
        uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK);

        success = true;

        if (command < Commands.FOURTH_IF_BOUNDARY) {
            if (command < Commands.SECOND_IF_BOUNDARY) {
                // 0x00 <= command < 0x08
                if (command < Commands.FIRST_IF_BOUNDARY) {
                    if (command == Commands.V3_SWAP_EXACT_IN) {
                        // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
                        address recipient;
                        uint256 amountIn;
                        uint256 amountOutMin;
                        bool payerIsUser;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountIn := calldataload(add(inputs.offset, 0x20))
                            amountOutMin := calldataload(add(inputs.offset, 0x40))
                            // 0x60 offset is the path, decoded below
                            payerIsUser := calldataload(add(inputs.offset, 0x80))
                        }
                        bytes calldata path = inputs.toBytes(3);
                        address payer = payerIsUser ? lockedBy : address(this);
                        v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
                    } else if (command == Commands.V3_SWAP_EXACT_OUT) {
                        // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
                        address recipient;
                        uint256 amountOut;
                        uint256 amountInMax;
                        bool payerIsUser;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountOut := calldataload(add(inputs.offset, 0x20))
                            amountInMax := calldataload(add(inputs.offset, 0x40))
                            // 0x60 offset is the path, decoded below
                            payerIsUser := calldataload(add(inputs.offset, 0x80))
                        }
                        bytes calldata path = inputs.toBytes(3);
                        address payer = payerIsUser ? lockedBy : address(this);
                        v3SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer);
                    } else if (command == Commands.PERMIT2_TRANSFER_FROM) {
                        // equivalent: abi.decode(inputs, (address, address, uint160))
                        address token;
                        address recipient;
                        uint160 amount;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            amount := calldataload(add(inputs.offset, 0x40))
                        }
                        permit2TransferFrom(token, lockedBy, map(recipient), amount);
                    } else if (command == Commands.PERMIT2_PERMIT_BATCH) {
                        (IAllowanceTransfer.PermitBatch memory permitBatch,) =
                            abi.decode(inputs, (IAllowanceTransfer.PermitBatch, bytes));
                        bytes calldata data = inputs.toBytes(1);
                        PERMIT2.permit(lockedBy, permitBatch, data);
                    } else if (command == Commands.SWEEP) {
                        // equivalent:  abi.decode(inputs, (address, address, uint256))
                        address token;
                        address recipient;
                        uint160 amountMin;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            amountMin := calldataload(add(inputs.offset, 0x40))
                        }
                        Payments.sweep(token, map(recipient), amountMin);
                    } else if (command == Commands.TRANSFER) {
                        // equivalent:  abi.decode(inputs, (address, address, uint256))
                        address token;
                        address recipient;
                        uint256 value;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            value := calldataload(add(inputs.offset, 0x40))
                        }
                        Payments.pay(token, map(recipient), value);
                    } else if (command == Commands.PAY_PORTION) {
                        // equivalent:  abi.decode(inputs, (address, address, uint256))
                        address token;
                        address recipient;
                        uint256 bips;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            bips := calldataload(add(inputs.offset, 0x40))
                        }
                        Payments.payPortion(token, map(recipient), bips);
                    } else {
                        // placeholder area for command 0x07
                        revert InvalidCommandType(command);
                    }
                    // 0x08 <= command < 0x10
                } else {
                    if (command == Commands.V2_SWAP_EXACT_IN) {
                        // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
                        address recipient;
                        uint256 amountIn;
                        uint256 amountOutMin;
                        bool payerIsUser;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountIn := calldataload(add(inputs.offset, 0x20))
                            amountOutMin := calldataload(add(inputs.offset, 0x40))
                            // 0x60 offset is the path, decoded below
                            payerIsUser := calldataload(add(inputs.offset, 0x80))
                        }
                        address[] calldata path = inputs.toAddressArray(3);
                        address payer = payerIsUser ? lockedBy : address(this);
                        v2SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
                    } else if (command == Commands.V2_SWAP_EXACT_OUT) {
                        // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
                        address recipient;
                        uint256 amountOut;
                        uint256 amountInMax;
                        bool payerIsUser;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountOut := calldataload(add(inputs.offset, 0x20))
                            amountInMax := calldataload(add(inputs.offset, 0x40))
                            // 0x60 offset is the path, decoded below
                            payerIsUser := calldataload(add(inputs.offset, 0x80))
                        }
                        address[] calldata path = inputs.toAddressArray(3);
                        address payer = payerIsUser ? lockedBy : address(this);
                        v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer);
                    } else if (command == Commands.PERMIT2_PERMIT) {
                        // equivalent: abi.decode(inputs, (IAllowanceTransfer.PermitSingle, bytes))
                        IAllowanceTransfer.PermitSingle calldata permitSingle;
                        assembly {
                            permitSingle := inputs.offset
                        }
                        bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5)
                        PERMIT2.permit(lockedBy, permitSingle, data);
                    } else if (command == Commands.WRAP_ETH) {
                        // equivalent: abi.decode(inputs, (address, uint256))
                        address recipient;
                        uint256 amountMin;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountMin := calldataload(add(inputs.offset, 0x20))
                        }
                        Payments.wrapETH(map(recipient), amountMin);
                    } else if (command == Commands.UNWRAP_WETH) {
                        // equivalent: abi.decode(inputs, (address, uint256))
                        address recipient;
                        uint256 amountMin;
                        assembly {
                            recipient := calldataload(inputs.offset)
                            amountMin := calldataload(add(inputs.offset, 0x20))
                        }
                        Payments.unwrapWETH9(map(recipient), amountMin);
                    } else if (command == Commands.PERMIT2_TRANSFER_FROM_BATCH) {
                        (IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails) =
                            abi.decode(inputs, (IAllowanceTransfer.AllowanceTransferDetails[]));
                        permit2TransferFrom(batchDetails, lockedBy);
                    } else if (command == Commands.BALANCE_CHECK_ERC20) {
                        // equivalent: abi.decode(inputs, (address, address, uint256))
                        address owner;
                        address token;
                        uint256 minBalance;
                        assembly {
                            owner := calldataload(inputs.offset)
                            token := calldataload(add(inputs.offset, 0x20))
                            minBalance := calldataload(add(inputs.offset, 0x40))
                        }
                        success = (ERC20(token).balanceOf(owner) >= minBalance);
                        if (!success) output = abi.encodePacked(BalanceTooLow.selector);
                    } else {
                        // placeholder area for command 0x0f
                        revert InvalidCommandType(command);
                    }
                }
                // 0x10 <= command
            } else {
                // 0x10 <= command < 0x18
                if (command < Commands.THIRD_IF_BOUNDARY) {
                    if (command == Commands.SEAPORT_V1_5) {
                        /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts.
                        ///     These orders pass control to the contract offerers during fufillment,
                        ///         allowing them to perform any number of destructive actions as a holder of the NFT.
                        ///     Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder
                        ///         to claim another NFT, the contract offerer can "steal" the claim during order fufillment.
                        ///     For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction.
                        ///     This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md
                        (uint256 value, bytes calldata data) = getValueAndData(inputs);
                        (success, output) = SEAPORT_V1_5.call{value: value}(data);
                    } else if (command == Commands.LOOKS_RARE_V2) {
                        // equivalent: abi.decode(inputs, (uint256, bytes))
                        uint256 value;
                        assembly {
                            value := calldataload(inputs.offset)
                        }
                        bytes calldata data = inputs.toBytes(1);
                        (success, output) = LOOKS_RARE_V2.call{value: value}(data);
                    } else if (command == Commands.NFTX) {
                        // equivalent: abi.decode(inputs, (uint256, bytes))
                        (uint256 value, bytes calldata data) = getValueAndData(inputs);
                        (success, output) = NFTX_ZAP.call{value: value}(data);
                    } else if (command == Commands.CRYPTOPUNKS) {
                        // equivalent: abi.decode(inputs, (uint256, address, uint256))
                        uint256 punkId;
                        address recipient;
                        uint256 value;
                        assembly {
                            punkId := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            value := calldataload(add(inputs.offset, 0x40))
                        }
                        (success, output) = CRYPTOPUNKS.call{value: value}(
                            abi.encodeWithSelector(ICryptoPunksMarket.buyPunk.selector, punkId)
                        );
                        if (success) ICryptoPunksMarket(CRYPTOPUNKS).transferPunk(map(recipient), punkId);
                        else output = abi.encodePacked(BuyPunkFailed.selector);
                    } else if (command == Commands.OWNER_CHECK_721) {
                        // equivalent: abi.decode(inputs, (address, address, uint256))
                        address owner;
                        address token;
                        uint256 id;
                        assembly {
                            owner := calldataload(inputs.offset)
                            token := calldataload(add(inputs.offset, 0x20))
                            id := calldataload(add(inputs.offset, 0x40))
                        }
                        success = (ERC721(token).ownerOf(id) == owner);
                        if (!success) output = abi.encodePacked(InvalidOwnerERC721.selector);
                    } else if (command == Commands.OWNER_CHECK_1155) {
                        // equivalent: abi.decode(inputs, (address, address, uint256, uint256))
                        address owner;
                        address token;
                        uint256 id;
                        uint256 minBalance;
                        assembly {
                            owner := calldataload(inputs.offset)
                            token := calldataload(add(inputs.offset, 0x20))
                            id := calldataload(add(inputs.offset, 0x40))
                            minBalance := calldataload(add(inputs.offset, 0x60))
                        }
                        success = (ERC1155(token).balanceOf(owner, id) >= minBalance);
                        if (!success) output = abi.encodePacked(InvalidOwnerERC1155.selector);
                    } else if (command == Commands.SWEEP_ERC721) {
                        // equivalent: abi.decode(inputs, (address, address, uint256))
                        address token;
                        address recipient;
                        uint256 id;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            id := calldataload(add(inputs.offset, 0x40))
                        }
                        Payments.sweepERC721(token, map(recipient), id);
                    }
                    // 0x18 <= command < 0x1f
                } else {
                    if (command == Commands.X2Y2_721) {
                        (success, output) = callAndTransfer721(inputs, X2Y2);
                    } else if (command == Commands.SUDOSWAP) {
                        // equivalent: abi.decode(inputs, (uint256, bytes))
                        (uint256 value, bytes calldata data) = getValueAndData(inputs);
                        (success, output) = SUDOSWAP.call{value: value}(data);
                    } else if (command == Commands.NFT20) {
                        // equivalent: abi.decode(inputs, (uint256, bytes))
                        (uint256 value, bytes calldata data) = getValueAndData(inputs);
                        (success, output) = NFT20_ZAP.call{value: value}(data);
                    } else if (command == Commands.X2Y2_1155) {
                        (success, output) = callAndTransfer1155(inputs, X2Y2);
                    } else if (command == Commands.FOUNDATION) {
                        (success, output) = callAndTransfer721(inputs, FOUNDATION);
                    } else if (command == Commands.SWEEP_ERC1155) {
                        // equivalent: abi.decode(inputs, (address, address, uint256, uint256))
                        address token;
                        address recipient;
                        uint256 id;
                        uint256 amount;
                        assembly {
                            token := calldataload(inputs.offset)
                            recipient := calldataload(add(inputs.offset, 0x20))
                            id := calldataload(add(inputs.offset, 0x40))
                            amount := calldataload(add(inputs.offset, 0x60))
                        }
                        Payments.sweepERC1155(token, map(recipient), id, amount);
                    } else if (command == Commands.ELEMENT_MARKET) {
                        // equivalent: abi.decode(inputs, (uint256, bytes))
                        (uint256 value, bytes calldata data) = getValueAndData(inputs);
                        (success, output) = ELEMENT_MARKET.call{value: value}(data);
                    } else {
                        // placeholder for command 0x1f
                        revert InvalidCommandType(command);
                    }
                }
            }
            // 0x20 <= command
        } else {
            if (command == Commands.SEAPORT_V1_4) {
                /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts.
                ///     These orders pass control to the contract offerers during fufillment,
                ///         allowing them to perform any number of destructive actions as a holder of the NFT.
                ///     Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder
                ///         to claim another NFT, the contract offerer can "steal" the claim during order fufillment.
                ///     For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction.
                ///     This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md
                (uint256 value, bytes calldata data) = getValueAndData(inputs);
                (success, output) = SEAPORT_V1_4.call{value: value}(data);
            } else if (command == Commands.EXECUTE_SUB_PLAN) {
                bytes calldata _commands = inputs.toBytes(0);
                bytes[] calldata _inputs = inputs.toBytesArray(1);
                (success, output) =
                    (address(this)).call(abi.encodeWithSelector(Dispatcher.execute.selector, _commands, _inputs));
            } else if (command == Commands.APPROVE_ERC20) {
                ERC20 token;
                RouterImmutables.Spenders spender;
                assembly {
                    token := calldataload(inputs.offset)
                    spender := calldataload(add(inputs.offset, 0x20))
                }
                Payments.approveERC20(token, spender);
            } else {
                // placeholder area for commands 0x22-0x3f
                revert InvalidCommandType(command);
            }
        }
}

概要
指定されたコマンドタイプに基づいて、対応する処理を実行する関数。
デコードされたコマンドタイプによって異なる操作を行います。

詳細
与えられたコマンドタイプに基づいて、対応する処理を選択し、その処理を実行します。
コマンドタイプに応じて、異なる引数をデコードして関数に渡します。

引数 or パラメータ

  • commandType
    • 実行するコマンドのタイプを示す1バイトの値。
  • inputs
    • コマンドの実行に必要な引数やデータのバイト列。

戻り値

  • success
    • コマンドの実行が成功した場合にtrue、失敗した場合にfalseを返します。
  • output
    • コマンドの実行結果やエラーメッセージなどが格納されたバイト列を返します。

execute

execute
function execute(bytes calldata commands, bytes[] calldata inputs) external payable virtual;

概要
エンコードされたコマンドとそれに対応する入力データを実行する関数。
複数のコマンドと対応する入力データが提供され、それぞれのコマンドを順番に実行します。

詳細
エンコードされたコマンドとそれに対応する入力データを受け取り、それぞれのコマンドを順番に実行します。
コマンドの実行結果やエラーメッセージは、個別のコマンドごとに戻り値として提供されます。

引数

  • commands
    • 連結されたコマンドのバイト列。
    • 各コマンドは1バイトの長さです。
  • inputs
    • 各コマンドに対するABIエンコードされた入力データのバイト列の配列。

戻り値
なし


callAndTransfer721

callAndTransfer721
function callAndTransfer721(bytes calldata inputs, address protocol)
        internal
        returns (bool success, bytes memory output)
    {
        // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256))
        (uint256 value, bytes calldata data) = getValueAndData(inputs);
        address recipient;
        address token;
        uint256 id;
        assembly {
            // 0x00 and 0x20 offsets are value and data, above
            recipient := calldataload(add(inputs.offset, 0x40))
            token := calldataload(add(inputs.offset, 0x60))
            id := calldataload(add(inputs.offset, 0x80))
        }
        (success, output) = protocol.call{value: value}(data);
        if (success) ERC721(token).safeTransferFrom(address(this), map(recipient), id);
}

概要
指定されたプロトコルを呼び出し、それによって購入されたERC721トークンを指定された受取アドレスに転送する関数。

詳細
指定されたプロトコルを呼び出して購入処理を行い、その後、購入されたERC721トークンを指定された受取アドレスに転送します。
関数内部で、受取アドレス、トークンアドレス、トークンIDなどの情報をデコードし、トランザクションを実行します。

引数

  • inputs
    • プロトコルとERC721トークンの転送に関する入力データがエンコードされたバイト列。
  • protocol
    • コールデータを渡すためのプロトコルのアドレス。

戻り値

  • success
    • コマンドの実行が成功した場合にtrue、失敗した場合にfalseを返します。
  • output
    • コマンドの実行結果やエラーメッセージなどが格納されたバイト列を返します。

callAndTransfer1155

callAndTransfer1155
function callAndTransfer1155(bytes calldata inputs, address protocol)
        internal
        returns (bool success, bytes memory output)
    {
        // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256, uint256))
        (uint256 value, bytes calldata data) = getValueAndData(inputs);
        address recipient;
        address token;
        uint256 id;
        uint256 amount;
        assembly {
            // 0x00 and 0x20 offsets are value and data, above
            recipient := calldataload(add(inputs.offset, 0x40))
            token := calldataload(add(inputs.offset, 0x60))
            id := calldataload(add(inputs.offset, 0x80))
            amount := calldataload(add(inputs.offset, 0xa0))
        }
        (success, output) = protocol.call{value: value}(data);
        if (success) ERC1155(token).safeTransferFrom(address(this), map(recipient), id, amount, new bytes(0));
}

概要
指定されたプロトコルを呼び出し、それによって購入されたERC1155トークンを指定された受取アドレスに転送する関数。

詳細
指定されたプロトコルを呼び出して購入処理を行い、その後、購入されたERC1155トークンを指定された受取アドレスに転送します。
関数内部で、受取アドレス、トークンアドレス、トークンID、数量などの情報をデコードし、トランザクションを実行します。

引数

  • inputs
    • プロトコルとERC1155トークンの転送に関する入力データがエンコードされたバイト列。
  • protocol
    • コールデータを渡すためのプロトコルのアドレス。

戻り値

  • success
    • コマンドの実行が成功した場合にtrue、失敗した場合にfalseを返します。
  • output
    • コマンドの実行結果やエラーメッセージなどが格納されたバイト列を返します。

getValueAndData

getValueAndData
function getValueAndData(bytes calldata inputs) internal pure returns (uint256 value, bytes calldata data) {
        assembly {
            value := calldataload(inputs.offset)
        }
        data = inputs.toBytes(1);
}

概要
入力データからvaluedataパラメータを抽出するためのヘルパー関数。

詳細
入力データからvaluedataの2つのパラメータを抽出します。
valueは最初のパラメータとして、dataは2番目のパラメータとして想定されています。

引数 or パラメータ

  • inputs
    • valuedataパラメータを含むバイト列。

戻り値

  • value
    • 256ビットの整数値。
  • data
    • データのバイト列。

RewardsCollector

collectRewards

collectRewards
function collectRewards(bytes calldata looksRareClaim) external {
        (bool success,) = LOOKS_RARE_REWARDS_DISTRIBUTOR.call(looksRareClaim);
        if (!success) revert UnableToClaim();

        uint256 balance = LOOKS_RARE_TOKEN.balanceOf(address(this));
        LOOKS_RARE_TOKEN.transfer(ROUTER_REWARDS_DISTRIBUTOR, balance);
        emit RewardsSent(balance);
}

概要
報酬を受け取り、転送する関数。
LOOKS_RARE_REWARDS_DISTRIBUTORを呼び出して報酬を受け取り、LOOKS_RARE_TOKENの残高を取得し、ROUTER_REWARDS_DISTRIBUTORに転送します。
操作の成功や失敗に応じてイベントを発行します。

詳細
まず、LOOKS_RARE_REWARDS_DISTRIBUTORcall関数を使用してlooksRareClaimを実行し、報酬を受け取ります。
その後、LOOKS_RARE_TOKENの残高を取得し、その残高をROUTER_REWARDS_DISTRIBUTORに転送します。
最後に、転送した報酬の量をRewardsSentイベントとして記録します。

引数

  • looksRareClaim
    • 報酬を受け取るためのデータ。

注意
revert UnableToClaim();の部分は、successfalseの場合、報酬の受け取りに失敗したことを示すエラーメッセージとして処理を中断します。

Callbacks

onERC721Received

onERC721Received
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC721Received.selector;
}

概要
ERC721トークンが転送されたときに呼び出される関数。
ERC721トークンを受け取るためのコントラクトで実装されます。

詳細
ERC721トークンがこのコントラクトに送信されたときに自動的に実行されます。
通常、トークンの受け入れに関する特定の処理やイベントの発行などが行われます。

引数

  • operator
    • トークンを転送したオペレーターのアドレス。
  • from
    • トークンを送信したアドレス。
  • tokenId
    • 転送されたトークンのID。
  • data
    • 任意のデータ。

戻り値

  • bytes4
    • この関数が実装されていることを示す関数シグネチャ。

onERC1155Received

onERC1155Received
function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) {
        return this.onERC1155Received.selector;
}

概要
ERC1155トークンが転送されたときに呼び出される関数。
ERC1155トークンを受け取るためのコントラクトで実装されます。

詳細
ERC1155トークンがこのコントラクトに送信されたときに呼び出されます。
ERC1155トークンは、複数の異なるトークンIDと数量を含む場合があります。

引数

  • operator
    • トークンを転送したオペレーターのアドレス。
  • from
    • トークンを送信したアドレス。
  • id
    • 転送されたトークンのID。
  • value
    • 転送されたトークンの数量。
  • data
    • 任意のデータ。

戻り値

  • bytes4
    • この関数が実装されていることを示す関数シグネチャ。

onERC1155BatchReceived

onERC1155BatchReceived

概要
複数のERC1155トークンがまとめて転送されたときに呼び出される関数。

詳細
複数のERC1155トークンがこのコントラクトにまとめて送信されたときに呼び出されます。
それぞれのトークンは異なるトークンIDと数量を持つ可能性があります。

引数

  • operator
    • トークンを転送したオペレーターのアドレス。
  • from
    • トークンを送信したアドレス。
  • ids
    • 転送されたトークンのIDの配列。
  • values
    • 転送されたトークンの数量の配列。
  • data
    • 任意のデータ。

戻り値

  • bytes4
    • この関数が実装されていることを示す関数シグネチャ。

supportsInterface

概要
指定されたインターフェースがサポートされているかどうかをチェックする関数。
ERC165のインターフェースIDを指定して、そのインターフェースがサポートされているかどうかを確認できます。

詳細
指定されたインターフェースIDがこのコントラクトがサポートしているERC165のインターフェースIDと一致するかどうかを確認します。

引数

  • interfaceId
    • 確認したいインターフェースのID。

戻り値

  • bool
    • 指定されたインターフェースがサポートされている場合はtrue、そうでない場合はfalseを返す。

LockAndMsgSender

ロック機構とメッセージ送信者に関する機能を提供するコントラクト。
外部からのアクセスを制限し、ロック状態でのコマンド実行を防ぐ仕組みを提供します。
また、メッセージ送信者に関する情報を管理し、特定のコマンドでその情報を使用します。


ContractLocked

ContractLocked
error ContractLocked();

概要
ロック状態のコントラクトで関数が呼び出された時に発生するエラー。
コントラクトがロックされている状態での操作を防ぐために使用されます。

詳細
このエラーは、isNotLocked 修飾子内でロック状態のチェックが行われ、ロックされている場合にこのエラーが発生します。
これにより、ロック中のコントラクトで関数が呼び出されるのを防ぎます。


NOT_LOCKED_FLAG

NOT_LOCKED_FLAG
address internal constant NOT_LOCKED_FLAG = address(1);

概要
ロック状態を示すフラグとして使用されるアドレス。

詳細
このアドレスは、コントラクトがロックされていない状態を示すために使用されます。
lockedBy 変数がこのアドレスでない場合、コントラクトはロックされているとみなされます。


lockedBy

lockedBy
address internal lockedBy = NOT_LOCKED_FLAG;

概要
コントラクトのロック状態を管理するためのアドレス変数。
この変数は、コントラクトがロックされているときにロックを行ったアドレスを保持します。

詳細
この変数は、isNotLocked 修飾子内でのみ変更できます。 ロック状態の間、この変数にはロックを行ったアドレスが格納され、ロック解除後に NOT_LOCKED_FLAG`に戻ります。


isNotLocked

isNotLocked
modifier isNotLocked() {
        if (msg.sender != address(this)) {
            if (lockedBy != NOT_LOCKED_FLAG) revert ContractLocked();
            lockedBy = msg.sender;
            _;
            lockedBy = NOT_LOCKED_FLAG;
        } else {
            _;
        }
}

概要
コントラクトがロックされていないことを確認する修飾子。
関数実行前にこの修飾子が適用され、ロック状態での関数実行を防ぎます。

詳細
この修飾子は、関数実行前にロック状態を確認し、ロック中の場合はエラーを発生させます。
ロックされていない場合、関数の実行を許可します。


map

map
function map(address recipient) internal view returns (address) {
        if (recipient == Constants.MSG_SENDER) {
            return lockedBy;
        } else if (recipient == Constants.ADDRESS_THIS) {
            return address(this);
        } else {
            return recipient;
        }
}

概要
コマンドの宛先アドレスを計算する関数。
特定のフラグやアドレスに基づいて、宛先アドレスを返します。

詳細
この関数は、recipientパラメータに基づいて宛先アドレスを計算します。Constants.MSG_SENDERConstants.ADDRESS_THISの場合、それぞれロックを行ったアドレスやコントラクト自体のアドレスを返します。

引数

  • recipient
    • コマンドの宛先となるアドレスまたはフラグ。

戻り値

  • address
    • 計算されたコマンドの宛先アドレス。

モジュール

Payments

pay

pay
function pay(address token, address recipient, uint256 value) internal {
        if (token == Constants.ETH) {
            recipient.safeTransferETH(value);
        } else {
            if (value == Constants.CONTRACT_BALANCE) {
                value = ERC20(token).balanceOf(address(this));
            }

            ERC20(token).safeTransfer(recipient, value);
        }
}

概要
ETHまたはERC20トークンを受取アドレスに支払う関数。

詳細
指定されたトークンと受取アドレスに指定した量を支払います。
トークンがETHの場合、safeTransferETH関数を使用してETHを安全に転送します。
トークンがERC20の場合、指定された量を受取アドレスに対して安全に転送します。
また、指定された量がConstants.CONTRACT_BALANCEの場合、コントラクトの残高を支払い額として使用します。

引数

  • token
    • 支払うトークンのアドレス。
  • recipient
    • 受取アドレスのアドレス。
  • value
    • 支払う量。

戻り値
なし


approveERC20

approveERC20
function approveERC20(ERC20 token, Spenders spender) internal {
        // check spender is one of our approved spenders
        address spenderAddress;
        /// @dev use 0 = Opensea Conduit for both Seaport v1.4 and v1.5
        if (spender == Spenders.OSConduit) spenderAddress = OPENSEA_CONDUIT;
        else if (spender == Spenders.Sudoswap) spenderAddress = SUDOSWAP;
        else revert InvalidSpender();

        // set approval
        token.safeApprove(spenderAddress, type(uint256).max);
}

概要
プロトコルがERC20トークンを支出できるように承認する関数。

詳細
指定されたERC20トークンとスペンダーアドレスを受け取り、トークンの最大量をスペンダーに承認します。
スペンダーアドレスは、事前に定義されたスペンダー(OpenseaConduitSudoswapなど)のいずれかである必要があります。

引数

  • token
    • 承認するトークンのERC20インスタンス。
  • spender
    • 承認するスペンダーアドレス。

戻り値
なし


payPortion

payPortion
function payPortion(address token, address recipient, uint256 bips) internal {
        if (bips == 0 || bips > FEE_BIPS_BASE) revert InvalidBips();
        if (token == Constants.ETH) {
            uint256 balance = address(this).balance;
            uint256 amount = (balance * bips) / FEE_BIPS_BASE;
            recipient.safeTransferETH(amount);
        } else {
            uint256 balance = ERC20(token).balanceOf(address(this));
            uint256 amount = (balance * bips) / FEE_BIPS_BASE;
            ERC20(token).safeTransfer(recipient, amount);
        }
}

概要
コントラクトのETHまたはERC20トークンの一部を受取アドレスに支払う関数。

詳細
指定されたトークンと受取アドレスに指定した割合を支払います。
割合はbips(basis points)として与えられ、FEE_BIPS_BASE(ベースとなるbips)で割った値に基づいて計算されます。
トークンがETHの場合、コントラクトの残高から計算された支払額を安全に転送します。
トークンがERC20の場合も同様です。

引数

  • token
    • 支払うトークンのアドレス。
  • recipient
    • 受取アドレスのアドレス。
  • bips
    • 支払う割合(basis points)。

戻り値
なし


sweep

sweep
function sweep(address token, address recipient, uint256 amountMinimum) internal {
        uint256 balance;
        if (token == Constants.ETH) {
            balance = address(this).balance;
            if (balance < amountMinimum) revert InsufficientETH();
            if (balance > 0) recipient.safeTransferETH(balance);
        } else {
            balance = ERC20(token).balanceOf(address(this));
            if (balance < amountMinimum) revert InsufficientToken();
            if (balance > 0) ERC20(token).safeTransfer(recipient, balance);
        }
}

概要
コントラクトの全てのERC20トークンまたはETHを指定したアドレスに送る関数。

詳細
指定されたトークンと送付先アドレスに対して、コントラクトの残高が指定された最小額を超える場合にトークンを送ります。
ETHの場合、送付された額が指定された最小額を下回る場合はエラーを発生させます。

引数

  • token
    • 送付するトークンのアドレス。
  • recipient
    • 送付先のアドレス。
  • amountMinimum
    • 最小の送付額。

戻り値
なし


sweepERC721

sweepERC721
function sweepERC721(address token, address recipient, uint256 id) internal {
        ERC721(token).safeTransferFrom(address(this), recipient, id);
}

概要
コントラクト内のERC721トークンを指定したアドレスに送る関数。

詳細
この関数は、指定されたERC721トークンと受取アドレス、トークンのIDを受け取り、コントラクト内の指定されたERC721トークンを受取アドレスに安全に転送します。

引数 or パラメータ

  • token
    • 送付するERC721トークンのアドレス。
  • recipient
    • 受取アドレス。
  • id
    • 送付するERC721トークンのID。

戻り値
なし


sweepERC1155

sweepERC1155
function sweepERC1155(address token, address recipient, uint256 id, uint256 amountMinimum) internal {
        uint256 balance = ERC1155(token).balanceOf(address(this), id);
        if (balance < amountMinimum) revert InsufficientToken();
        ERC1155(token).safeTransferFrom(address(this), recipient, id, balance, bytes(''));
}

概要
コントラクト内のERC1155トークンを指定したアドレスに送る関数。

詳細
指定されたERC1155トークンと受取アドレス、トークンのID、最小額を受け取り、コントラクト内の指定されたERC1155トークンを受取アドレスに安全に転送します。

引数

  • token
    • 送付するERC1155トークンのアドレス。
  • recipient
    • 受取アドレス。
  • id
    • 送付するERC1155トークンID。
  • amountMinimum
    • 最小の送付額。

戻り値
なし


wrapETH

wrapETH
function wrapETH(address recipient, uint256 amount) internal {
        if (amount == Constants.CONTRACT_BALANCE) {
            amount = address(this).balance;
        } else if (amount > address(this).balance) {
            revert InsufficientETH();
        }
        if (amount > 0) {
            WETH9.deposit{value: amount}();
            if (recipient != address(this)) {
                WETH9.transfer(recipient, amount);
            }
        }
}

概要
指定した量のETHWETHに変換し、指定した受取アドレスに送る関数。

詳細
ETHWETH(Wrapped Ether) に変換するプロセスを実行します。
WETHは、Ethereumネットワーク上でトークンとして取り扱うことができるETHのラッパートークンです。
指定された量のETHWETHに変換し、受取アドレスに送ります。
また、特定の場合には、コントラクトのETH残高を全て変換することもできます。

引数:

  • recipient
    • WETHを受け取るアドレス。
  • amount
    • 変換するETHの量(Constants.CONTRACT_BALANCEと指定することで、コントラクトのETH残高を全て変換する)。

unwrapWETH9

unwrapWETH9
function unwrapWETH9(address recipient, uint256 amountMinimum) internal {
        uint256 value = WETH9.balanceOf(address(this));
        if (value < amountMinimum) {
            revert InsufficientETH();
        }
        if (value > 0) {
            WETH9.withdraw(value);
            if (recipient != address(this)) {
                recipient.safeTransferETH(value);
            }
        }
}

概要
コントラクト内の全てのWETHETHに変換し、指定した最小量のETHを以上の場合に受け取りアドレスに送る関数。

詳細
コントラクト内の全てのWETHETHに変換するプロセスを実行します。
指定した最小量のETHを以上の場合、ETHを受取アドレスに送ります。

引数:

  • recipient
    • ETHを受け取るアドレス。
  • amountMinimum
    • 送信する最小量のETH

Permit2Payments

permit2TransferFrom

permit2TransferFrom
function permit2TransferFrom(address token, address from, address to, uint160 amount) internal {
        PERMIT2.transferFrom(from, to, amount, token);
}

概要
Permit2コントラクトのtransferFromを実行する関数。

詳細
トークンを指定された送信元アドレスから受信先アドレスへ指定された量だけ転送します。

引数

  • token
    • 転送するトークンのアドレス。
  • from
    • トークンを転送する元のアドレス。
  • to
    • トークンを転送する先のアドレス。
  • amount
    • 転送するトークン量。

permit2TransferFrom

permit2TransferFrom
function permit2TransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails, address owner)
        internal
    {
        uint256 batchLength = batchDetails.length;
        for (uint256 i = 0; i < batchLength; ++i) {
            if (batchDetails[i].from != owner) revert FromAddressIsNotOwner();
        }
        PERMIT2.transferFrom(batchDetails);
}

概要
Permit2トークンのバッチ転送を実行する関数。

詳細
指定されたバッチの各転送詳細に基づいて、トークンを転送します。
オーナーアドレスが一致しない場合にはエラーを発生させます。

引数

  • batchDetails
    • 各転送詳細を含む配列。
  • owner
    • オーナーアドレス。

payOrPermit2Transfer

payOrPermit2Transfer
function payOrPermit2Transfer(address token, address payer, address recipient, uint256 amount) internal {
        if (payer == address(this)) pay(token, recipient, amount);
        else permit2TransferFrom(token, payer, recipient, amount.toUint160());
}

概要
支払いまたはPermit2コントラクトのtransferFromを実行する関数。
支払いの際にはpayerが自身のアドレスと一致するかどうかを確認し、そうでない場合にはPermit2コントラクトのtransferFromを行います。

詳細
指定された条件に基づいて支払いまたはPermit2コントラクトのtransferFromを実行します。
payerが自身のアドレスであれば、通常の支払いを行います。
それ以外の場合は、Permit2コントラクトのtransferFrom操作を行い、指定されたトークンを指定された受け取りアドレスへ転送します。

引数

  • token
    • 転送するトークンのアドレス。
  • payer
    • 支払いを行うアドレス。
  • recipient
    • トークンを受け取るアドレス。
  • amount
    • 転送するトークンの量。

ライブラリ

Commands

FLAG_ALLOW_REVERT

FLAG_ALLOW_REVERT
bytes1 internal constant FLAG_ALLOW_REVERT = 0x80;

説明
コマンドのフラグとして使用されます。
特定のビットが立っているかどうかを確認するために使用され、リバートを許可するかどうかを示すフラグです。


COMMAND_TYPE_MASK

COMMAND_TYPE_MASK
bytes1 internal constant COMMAND_TYPE_MASK = 0x3f;

説明
コマンドのタイプを取得するためのマスクとして使用されます。
コマンドタイプを抽出するために使用されるビットマスクです。


V3_SWAP_EXACT_IN

V3_SWAP_EXACT_IN
uint256 constant V3_SWAP_EXACT_IN = 0x00;

説明
コマンドタイプを示すコマンドの一部です。
バージョン3のスワップ(Exact In)コマンドを表します。
トークンの入力量が指定され、出力量が計算されるスワップ操作です。


V3_SWAP_EXACT_OUT

V3_SWAP_EXACT_OUT
uint256 constant V3_SWAP_EXACT_OUT = 0x01;

説明
コマンドタイプを示すコマンドの一部です。
バージョン3のスワップ(Exact Out)コマンドを表します。
トークンの出力量が指定され、入力量が計算されるスワップ操作です。


PERMIT2_TRANSFER_FROM

PERMIT2_TRANSFER_FROM
uint256 constant PERMIT2_TRANSFER_FROM = 0x02;

説明
この変数は、コマンドタイプを示すコマンドの一部です。トークンのトランスファー操作を許可するための permit 機能を使用した際のコマンドを表します。


PERMIT2_PERMIT_BATCH

PERMIT2_PERMIT_BATCH
uint256 constant PERMIT2_PERMIT_BATCH = 0x03;

説明
コマンドタイプを示すコマンドの一部です。
複数のトークンに対してpermit機能を使用した際のバッチ処理のコマンドを表します。


SWEEP

SWEEP
uint256 constant SWEEP = 0x04;

説明
コマンドタイプを示すコマンドの一部です。
トークンを集める(sweep)操作を表します。
複数のアドレスからトークンを集めるために使用されるコマンドです。


TRANSFER

TRANSFER
uint256 constant TRANSFER = 0x05;

説明
コマンドタイプを示すコマンドの一部です。
トークンのトランスファー操作を行うためのコマンドを表します。


PAY_PORTION

PAY_PORTION
uint256 constant PAY_PORTION = 0x06;

説明
コマンドタイプを示すコマンドの一部です。
特定のトークンの一部を支払うためのコマンドを表します。
たとえば、特定のトークンの所有者に対してトークンの一部を支払う際に使用されます。


FIRST_IF_BOUNDARY

FIRST_IF_BOUNDARY
uint256 constant FIRST_IF_BOUNDARY = 0x08;

説明
コマンドタイプが一定範囲内にある場合、特定の条件を満たすコマンドを表す境界を示します。
この境界を超えるコマンドタイプは別の条件で評価されます。


V2_SWAP_EXACT_IN

V2_SWAP_EXACT_IN
uint256 constant V2_SWAP_EXACT_IN = 0x08;

説明
コマンドタイプを示すコマンドの一部です。
バージョン2のスワップ(Exact In)コマンドを表します。
トークンの入力量が指定され、出力量が計算されるスワップ操作です。


V2_SWAP_EXACT_OUT

V2_SWAP_EXACT_OUT
uint256 constant V2_SWAP_EXACT_OUT = 0x09;

説明
コマンドタイプを示すコマンドの一部です。
バージョン2のスワップ(Exact Out)コマンドを表します。
トークンの出力量が指定され、入力量が計算されるスワップ操作です。


PERMIT2_PERMIT

PERMIT2_PERMIT
uint256 constant PERMIT2_PERMIT = 0x0a;

説明
コマンドタイプを示すコマンドの一部です。
permit機能を使用したトークンのトランスファー操作を行うためのコマンドを表します。


WRAP_ETH

WRAP_ETH
uint256 constant WRAP_ETH = 0x0b;

説明
コマンドタイプを示すコマンドの一部です。
Ether(ETH) をラップするためのコマンドを表します。
ETHを特定のERC20トークンに変換する操作です。


UNWRAP_WETH

UNWRAP_WETH
uint256 constant UNWRAP_WETH = 0x0c;

説明
コマンドタイプを示すコマンドの一部です。
Wrapped Ether(WETH) をアンラップするためのコマンドを表します。
WETHETHに戻す操作です。


PERMIT2_TRANSFER_FROM_BATCH

PERMIT2_TRANSFER_FROM_BATCH
uint256 constant PERMIT2_TRANSFER_FROM_BATCH = 0x0d;

説明
コマンドタイプを示すコマンドの一部です。
複数のトークンに対してpermit機能を使用した際のバッチ処理のコマンドを表します。


BALANCE_CHECK_ERC20

BALANCE_CHECK_ERC20
uint256 constant BALANCE_CHECK_ERC20 = 0x0e;

説明
コマンドタイプを示すコマンドの一部です。
特定のERC20トークンの残高を確認するためのコマンドを表します。


SECOND_IF_BOUNDARY

SECOND_IF_BOUNDARY
uint256 constant SECOND_IF_BOUNDARY = 0x10;

説明
コマンドタイプが一定範囲内にある場合、特定の条件を満たすコマンドを表す境界を示します
この境界を超えるコマンドタイプは別の条件で評価されます。


SEAPORT_V1_5

SEAPORT_V1_5
uint256 constant SEAPORT_V1_5 = 0x10;

説明
コマンドタイプを示すコマンドの一部です。
SeaPortバージョン1.5の操作を表すコマンドを表します。

https://coinpost.jp/?p=359189#:~:text=SeaportはOpenSeaが5,に対応するものだ。&text=Seaportの主な改善,約35%25」節約可能。


LOOKS_RARE_V2

LOOKS_RARE_V2
uint256 constant LOOKS_RARE_V2 = 0x11;

説明
コマンドタイプを示すコマンドの一部です。
LooksRareバージョン2の操作を表すコマンドを表します。

https://looksrare.org/ja


NFTX

NFTX
uint256 constant NFTX = 0x12;

説明
コマンドタイプを示すコマンドの一部です。
NFTXに関する操作を表すコマンドを表します。

https://nftx.io/


CRYPTOPUNKS

CRYPTOPUNKS
uint256 constant CRYPTOPUNKS = 0x13;

説明
コマンドタイプを示すコマンドの一部です。
CryptoPunksに関する操作を表すコマンドを表します。


OWNER_CHECK_721

OWNER_CHECK_721
uint256 constant OWNER_CHECK_721 = 0x15;

説明
コマンドタイプを示すコマンドの一部です。
特定のERC721トークンの所有者を確認するためのコマンドを表します。


OWNER_CHECK_1155

OWNER_CHECK_1155
uint256 constant OWNER_CHECK_1155 = 0x16;

説明
この変数は、コマンドタイプを示すコマンドの一部です。
特定のERC1155トークンの所有者を確認するためのコマンドを表します。


THIRD_IF_BOUNDARY

THIRD_IF_BOUNDARY
uint256 constant THIRD_IF_BOUNDARY = 0x18;

説明
コマンドタイプが一定範囲内にある場合、特定の条件を満たすコマンドを表す境界を示します。
この境界を超えるコマンドタイプは別の条件で評価されます。


X2Y2_721

X2Y2_721
uint256 constant X2Y2_721 = 0x18;

説明
コマンドタイプを示すコマンドの一部です。
ERC721トークンを交換するためのコマンドを表します。


SUDOSWAP

SUDOSWAP
uint256 constant SUDOSWAP = 0x19;

説明
コマンドタイプを示すコマンドの一部です。
SudoSwapに関する操作を表すコマンドを表します。


NFT20

NFT20
uint256 constant NFT20 = 0x1a;

説明
コマンドタイプを示すコマンドの一部です。
ERC20に関する操作を表すコマンドを表します。


X2Y2_1155

X2Y2_1155
uint256 constant X2Y2_1155 = 0x1b;

説明
コマンドタイプを示すコマンドの一部です。
ERC1155トークンを交換するためのコマンドを表します。


FOUNDATION

FOUNDATION
uint256 constant FOUNDATION = 0x1c;

説明
コマンドタイプを示すコマンドの一部です。
Foundationに関する操作を表すコマンドを表します。


SWEEP_ERC1155

SWEEP_ERC1155
uint256 constant SWEEP_ERC1155 = 0x1d;

説明
コマンドタイプを示すコマンドの一部です。
ERC1155トークンを集める(sweep)操作を表します。


ELEMENT_MARKET

ELEMENT_MARKET
uint256 constant ELEMENT_MARKET = 0x1e;

説明
コマンドタイプを示すコマンドの一部です。
エレメントマーケットに関する操作を表すコマンドを表します。


FOURTH_IF_BOUNDARY

FOURTH_IF_BOUNDARY
uint256 constant FOURTH_IF_BOUNDARY = 0x20;

説明
コマンドタイプが一定範囲内にある場合、特定の条件を満たすコマンドを表す境界を示します。
この境界を超えるコマンドタイプは別の条件で評価されます。


SEAPORT_V1_4

SEAPORT_V1_4
uint256 constant SEAPORT_V1_4 = 0x20;

説明
コマンドタイプを示すコマンドの一部です。
SeaPortバージョン1.4の操作を表すコマンドを表します。


EXECUTE_SUB_PLAN

EXECUTE_SUB_PLAN
uint256 constant EXECUTE_SUB_PLAN = 0x21;

説明
コマンドタイプを示すコマンドの一部です。
サブプランを実行するためのコマンドを表します。
サブプランとは、コマンドの一連の実行手順のことを指します。


APPROVE_ERC20

APPROVE_ERC20
uint256 constant APPROVE_ERC20 = 0x22;

説明
コマンドタイプを示すコマンドの一部です。
ERC20トークンの承認を行うためのコマンドを表します。
他のアドレスがあるトークンを操作できるように承認します。


Constants

もちろんです。以下に、上記ライブラリ内の各変数についてわかりやすく説明します。

CONTRACT_BALANCE

:::details

uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000;

:::

説明
特定のトークンの残高を入力として使用する場合に識別するために使用されます
この値は、最も重要なビットに単一の1がある形式で表現され、最も上位ビットに1が設定されています。


ALREADY_PAID

:::details

uint256 internal constant ALREADY_PAID = 0;

:::

説明
v2のペアがすでに入力トークンを受け取った場合に識別するために使用されます。


ETH

:::details

address internal constant ETH = address(0);

:::

説明
ETH(イーサリアム)のトランザクションを識別するために使用されます。
具体的には、トークンではなくETHが送信される場合に使用されます。


MSG_SENDER

:::details

address internal constant MSG_SENDER = address(1);

:::

説明
特定のアドレスを識別するためのフラグです。
この変数の値はaddress(1)であり、トランザクションの送信者のアドレスを示します。


ADDRESS_THIS

:::details

address internal constant ADDRESS_THIS = address(2);

:::

説明
特定のアドレスを識別するためのフラグです。
この変数の値はaddress(2)であり、コントラクト自体のアドレスを示します。


ADDR_SIZE

:::details

uint256 internal constant ADDR_SIZE = 20;

:::

説明
アドレスがエンコードされたバイト列の長さを示します。
イーサリアムアドレスは20バイトで表されるため、この値は20です。


V3_FEE_SIZE

:::details

uint256 internal constant V3_FEE_SIZE = 3;

:::

説明
v3の手数料がエンコードされたバイト列の長さを示します。
具体的には、手数料情報は3バイトで表されます。


NEXT_V3_POOL_OFFSET

:::details

uint256 internal constant NEXT_V3_POOL_OFFSET = ADDR_SIZE + V3_FEE_SIZE;

:::

説明
次のv3プール情報のオフセットを示します。
特に、アドレスと手数料がエンコードされた次のプール情報の開始位置を指します。


V3_POP_OFFSET

:::details

uint256 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE;

:::

説明
エンコードされたプールキーのオフセットを示します。
プールキーは、トークンアドレスと手数料がエンコードされた情報を指します。


MULTIPLE_V3_POOLS_MIN_LENGTH

:::details

uint256 internal constant MULTIPLE_V3_POOLS_MIN_LENGTH = V3_POP_OFFSET + NEXT_V3_POOL_OFFSET;

:::

説明
複数のv3プール情報がエンコードされた際の最小エンコード長を示します。
複数のプール情報が含まれる場合、それぞれの情報を区別するためにこの最小の長さを持つエンコードが必要です。

イベント

RewardsSent

概要
報酬が送信されたときに発行されるイベント。

詳細
報酬が送信される場面で、その送信が行われたことを他のアプリケーションやユーザーに通知するために利用されます。

パラメータ

  • amount
    • 送信された報酬の金額を表します。

コード

コントラクト

  • UniversalRouter/contracts

ライブラリ

  • UniversalRouter/lib

ベースコントラクト

  • UniversalRouter/contracts/base

ライブラリ(コントラクト)

  • UniversalRouter/contracts/libraries

インターフェース

  • UniversalRouter/contracts/interfaces

モジュール

  • UniversalRouter/contracts/modules

最後に

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

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

https://chaldene.net/

https://qiita.com/cardene

DeCipher |"Read me" for All of Contracts

Discussion