はじめに

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

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

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

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

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

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

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

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

概要

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

目的

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

ユーザーに統一されたエクスペリエンスを提供することは、ユーザーがUniswapのdAppエコシステム内で異なるプロトコルやモジュールと対話する際に、一貫性のある操作や体験を提供することを意味します。

具体的には、ユーザーがトークンのスワップ、支払い、報酬の収集などの複雑なアクションを実行する際に、どのプロトコルやモジュールを使用すればいいのかを迷うことなく、簡単かつ円滑に行うことができるようになります。 ユーザーが異なるアクションを実行するために、別々のツールや手順を学ぶ必要はありません。 代わりに、UniversalRouterコントラクトが異なるアクションを適切な場所に導いてくれます。

この統一されたエクスペリエンスにより、ユーザーは技術的な詳細や違い、異なるアクションがどのように実行されるかを気にすることなく、dAppエコシステム内で必要なアクションを選択し、効率的に処理することができます。

責任

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

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

トランザクションのルーティングは、異なる種類のトランザクションを、それに適したモジュールやプロトコルにスムーズに繋ぐプロセスです。 例えば、トークンのスワップや支払い、報酬の収集などの異なるアクションは、それぞれ異なるモジュールやプロトコルで処理される場合があります。

このプロセスは特定のモジュールやプロトコルに依存せず、すべてのトランザクションが適切な方法で処理されることを確保します。 例えば、トークンのスワップが必要な場合、それはスワップ専用のモジュールにルーティングされ、支払いが必要な場合は支払い専用のモジュールにルーティングされます。 これにより、ユーザーが特定のアクションを実行する際に、どのモジュールやプロトコルを使用すればいいのかを気にすることなく、スムーズに処理できるようになります。

重要なのは、これらのトランザクションが正確で安全に実行されることです。 UniversalRouterは、各トランザクションを適切なモジュールやプロトコルに正確に導き、実行が間違いなく行われるようにします。 このプロセスによって、ユーザーはトランザクションの種類にかかわらず、信頼性の高い処理を期待できます。

インターオペラビリティ

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

インターオペラビリティは、異なるモジュールやプロトコルが協力して動作し、シームレスな連携を可能にする概念です。 これにより、ユーザーは技術的な詳細を知る必要なく、dAppエコシステム内の異なるコンポーネントと容易にコミュニケーションを取ることができます。

具体的には、異なるプロトコルやモジュールが互いに協力して動作するため、ユーザーはそれぞれの詳細や動作について深く理解する必要がありません。 UniversalRouterコントラクトを介して、これらの異なるコンポーネントが統合され、ユーザーは単一のインターフェースを通じて複雑なアクションを実行できるようになります。

例えば、トークンのスワップを考えてみましょう。 このアクションには複数のステップやプロトコルが関与する場合がありますが、ユーザーはその詳細を理解する必要はありません。 UniversalRouterコントラクトが異なるプロトコルを調整し、ユーザーが単にスワップしたいトークンを指定するだけで、適切なプロトコルが選択されて実行されます。

インターオペラビリティのおかげで、ユーザーは複雑なプロトコルの背後にある技術的な側面を理解することなく、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はコントラクトの初期化パラメータを保持するためのコントラクト。

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

引数

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

execute

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

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

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

引数

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

戻り値 なし


execute (override)

execute
solidity
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
solidity
function successRequired(bytes1 command) internal pure returns (bool) {
        return command & Commands.FLAG_ALLOW_REVERT == 0;
}

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

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

引数

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

戻り値

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

successRequired関数は、特定のコマンドが実行された際に処理結果が成功である必要があるかどうかを判定するための関数です。

コマンドが「FLAG_ALLOW_REVERT」という特定のフラグを含まない場合、つまり失敗してもリバート(状態を元に戻すこと)を許可しない場合、この関数は「成功である必要がある」を示す真偽値(true)を返します。 この判定は、トランザクションの実行において成功と失敗のハンドリング方法を制御するために使用されます。

例えば、あるトランザクションがトークンを移動するコマンドを含む場合を考えてみましょう。 このトランザクションが成功すればトークンが移動し、失敗すればトークンは移動しません。 ここで、このコマンドの成功が必要かどうかを判断するためにsuccessRequired関数が使用されます。

もしもコマンドに「FLAG_ALLOW_REVERT」フラグが含まれている場合、つまりトークン移動が失敗してもrevert(元に戻すこと)が可能な場合、successRequired関数は「成功が必要でない」と判断します。 なぜなら、トークン移動が失敗した場合でも、トランザクションはリバートによって元の状態に戻るからです。

一方で、コマンドに「FLAG_ALLOW_REVERT」フラグが含まれていない場合、つまりトークン移動が失敗した場合にはリバートが許可されず、状態が変更される場合、successRequired関数は「成功が必要」と判断します。 なぜなら、トークン移動が失敗した場合には元の状態に戻せないため、トランザクションの実行が必ず成功する必要があるからです。

このように、successRequired関数はコマンドの性質に基づいて、トランザクションの実行結果が成功でなければならないかどうかを判断し、トランザクションのハンドリング方法を制御します。


receive

receive
solidity
receive() external payable {}

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

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

Dispatcher

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

パラメータ

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

戻り値 なし


dispatch

dispatch
solidity
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
solidity
function execute(bytes calldata commands, bytes[] calldata inputs) external payable virtual;

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

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

引数

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

戻り値 なし


callAndTransfer721

callAndTransfer721
solidity
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
    • コマンドの実行結果やエラーメッセージなどが格納されたバイト列を返します。

callAndTransfer721関数は、ERC721トークンを購入して取引を実行し、その後トークンを指定した受取アドレスに転送する関数です。

例えば、以下のようなコマンドが与えられたとします:

solidity
callAndTransfer721(
    inputs,
    protocolAddress
);

ここで、inputsはABIエンコードされたバイト列で、購入に関する情報が含まれています。 protocolAddressは取引のプロトコルを指定するためのアドレスです。

callAndTransfer721関数内の処理は次のようになります。

  1. inputsのデコード inputsから必要な情報をデコードします。 この情報には、受取アドレス、トークンアドレス、トークンのIDなどが含まれます。

  2. 取引の実行 protocolAddressに対して取引を実行するために、コールを行います。 必要な金額も同時に送信されます。

  3. 取引成功の判定 取引の実行結果を確認し、成功したかどうかを判定します。

  4. トークンの転送: 取引が成功した場合、購入したERC721トークンを現在のコントラクトから指定した受取アドレスに転送します。

この関数は、購入とトークンの転送を一連のステップで行うためのものです。 具体的なコードやABIエンコードの内容によって、取引の詳細やトークンの種類などが変わりますが、基本的なプロセスは上記のような流れです。


callAndTransfer1155

callAndTransfer1155
solidity
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
solidity
function getValueAndData(bytes calldata inputs) internal pure returns (uint256 value, bytes calldata data) {
        assembly {
            value := calldataload(inputs.offset)
        }
        data = inputs.toBytes(1);
}

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

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

引数 or パラメータ

  • inputs
    • valueとdataパラメータを含むバイト列。

戻り値

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

getValueAndData関数は、与えられたバイト列から値とデータを取り出すための関数です。

例えば、以下のようなコードが与えられたとします:

solidity
(uint256 value, bytes memory data) = getValueAndData(inputs);

ここで、inputsはABIエンコードされたバイト列です。

getValueAndData関数内の処理は次のようになります。

  1. バイト列のデコード inputsから値とデータの情報をデコードします。 この処理によって、バイト列の先頭にある値とデータが取得されます。

  2. 値の取得 デコードされた情報から値(value)を取得します。 この値は通常、取引に使用するための金額などの数値データです。

  3. データの取得 デコードされた情報からデータ(data)を取得します。 このデータは通常、取引の呼び出しに必要なコールデータです。 このデータはバイト列として表現され、トランザクションの呼び出しに使用されます。

  4. 値とデータの返却 取得した値とデータを変数valueとdataに格納し、それらの変数を返します。

この関数を使用することで、与えられたバイト列から値とデータを正しく取り出すことができます。 具体的なコードやABIエンコードの内容によって、取得される値とデータの内容が変わりますが、基本的なプロセスは上記のような流れです。

RewardsCollector

collectRewards

collectRewards
solidity
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_DISTRIBUTORのcall関数を使用してlooksRareClaimを実行し、報酬を受け取ります。 その後、LOOKS_RARE_TOKENの残高を取得し、その残高をROUTER_REWARDS_DISTRIBUTORに転送します。 最後に、転送した報酬の量をRewardsSentイベントとして記録します。

引数

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

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

Callbacks

onERC721Received

onERC721Received
solidity
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
solidity
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
solidity

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

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

引数

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

戻り値

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

supportsInterface

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

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

引数

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

戻り値

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

LockAndMsgSender

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


ContractLocked

ContractLocked
solidity
error ContractLocked();

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

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


NOT_LOCKED_FLAG

NOT_LOCKED_FLAG
solidity
address internal constant NOT_LOCKED_FLAG = address(1);

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

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


lockedBy

lockedBy
solidity
address internal lockedBy = NOT_LOCKED_FLAG;

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

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


isNotLocked

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

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

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


map

map
solidity
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_SENDERやConstants.ADDRESS_THISの場合、それぞれロックを行ったアドレスやコントラクト自体のアドレスを返します。

引数

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

戻り値

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

map関数 仮想通貨取引を行うスマートコントラクトを考えましょう。 このコントラクトには「送金」コマンドがあり、送金を行う際には以下のようにして宛先アドレスを指定します。

  • recipientがConstants.MSG_SENDERの場合
    • ロックされているアドレスに送金される。
  • recipientがConstants.ADDRESS_THISの場合
    • このコントラクトのアドレスに送金される。
  • それ以外の場合
    • 指定されたアドレスに送金される。

例を見てみましょう。

  1. recipientがConstants.MSG_SENDERの場合
text
address recipient = Constants.MSG_SENDER;
address result = map(recipient);  // この場合、resultはlockedByのアドレスになる
  1. recipientがConstants.ADDRESS_THISの場合
text
address recipient = Constants.ADDRESS_THIS;
address result = map(recipient);  // この場合、resultはこのコントラクトのアドレスになる
  1. recipientが特定のアドレスの場合
text
address recipient = 0x1234567890abcdef1234567890abcdef12345678;  // 仮想アドレス
address result = map(recipient);  // この場合、resultは指定したアドレスになる

このように、map 関数は特定のフラグやアドレスに基づいて宛先アドレスを計算し、返す役割を果たします。 送金の場合以外にも、様々なコマンドにおいて宛先アドレスを動的に指定する際に利用されることがあります。

どのような状況でも、map関数は指定されたrecipientに応じて適切な宛先アドレスを計算し、それを戻り値として返します。

モジュール

Payments

pay

pay
solidity
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
solidity
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トークンとスペンダーアドレスを受け取り、トークンの最大量をスペンダーに承認します。 スペンダーアドレスは、事前に定義されたスペンダー(Opensea、Conduit、Sudoswapなど)のいずれかである必要があります。

引数

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

戻り値 なし


payPortion

payPortion
solidity
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)。

戻り値 なし

payPortionは、指定されたトークンのコントラクトから一定の割合のトークンを取得し、指定されたアドレスに送信するためのものです。

例えば、この関数を以下のように呼び出すとします。

solidity
payPortion(tokenAddress, recipientAddress, 500); // 500 bips (5%)

ここで、tokenAddressはトークンのアドレスを示し、recipientAddressは受取アドレスです。 500は支払いの割合を表しており、500 bipsは全体の残高の5%に相当します。

  1. 最初に、指定された割合500が FEE_BIPS_BASE(10000)以下であることを確認します。 これにより、割合が正当な値であることを確認します。 もし割合が0または10000を超える場合、InvalidBipsエラーが発生します。

  2. 次に、指定されたトークンがETHであるかどうかをチェックします。 もしトークンがETHであれば、コントラクトの残高を取得し、指定された割合に基づいて送信する量を計算します。 計算された量は、コントラクトの残高の5%(指定された割合)に相当します。

  3. もしトークンがETHではない場合、ERC20トークンとして処理します。 指定されたトークンのコントラクトからコントラクトのアドレスに対する残高を取得し、同様に割合に基づいて送信する量を計算します。

  4. 最後に、計算された量をrecipient.safeTransferETH(amount)またはERC20(token).safeTransfer(recipient, amount)を使用して指定されたアドレスに送信します。 この操作により、指定された割合のトークンが受取アドレスに送られます。

この関数を使用することで、異なるトークンをコントラクト内で一定の割合で分配することができます。


sweep

sweep
solidity
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
solidity
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
solidity
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
solidity
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);
            }
        }
}

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

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

引数:

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

unwrapWETH9

unwrapWETH9
solidity
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);
            }
        }
}

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

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

引数:

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

Permit2Payments

permit2TransferFrom

permit2TransferFrom
solidity
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
solidity
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
solidity
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
solidity
bytes1 internal constant FLAG_ALLOW_REVERT = 0x80;

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


COMMAND_TYPE_MASK

COMMAND_TYPE_MASK
solidity
bytes1 internal constant COMMAND_TYPE_MASK = 0x3f;

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

この定数は、特定のビットをマスクしてコマンドのタイプ情報を抽出するために使用されます。

バイト(bytes1)は8ビットのデータ型であり、各ビットは0または1の値を持ちます。 0x3fは16進数表記であり、バイナリで00111111に相当します。 このバイナリのパターンは、下位6ビットが1で上位2ビットが0であることを示しています。

このCOMMAND_TYPE_MASKは、コマンドのバイト表現からコマンドのタイプ情報だけを抽出するのに使用されます。 ビットマスクとして使用されることで、上位2ビットが無視され、下位6ビットの情報のみが保持されます。

以下はビットマスクの使用例です。

text
bytes1 commandByte = 0x27;  // 00100111 in binary
bytes1 commandType = commandByte & COMMAND_TYPE_MASK;

上記の例では、commandByteの下位6ビットをcommandTypeに抽出して格納します。 結果としてcommandTypeには、0x07(バイナリでは 00000111)が格納されることになります。 これにより、コマンドのタイプ情報を抽出するためにビットマスクが使用されていることがわかります。

このようにして、COMMAND_TYPE_MASKを使用することで、バイト表現のコマンドから必要な情報を取得するために特定のビットを抽出できます。

ビットマスク ビットマスクとは、特定のビットを抽出したり、操作したりするために、特定のビットパターンを使用するテクニックです。 ビットマスクは、ビットの特定の位置に対して論理演算を行い、そのビットを取得したり変更したりするのに使用されます。 主にプログラミングやデータ処理において使用されます。

以下は具体的な例を使ってビットマスクの概念を説明します。 考えているのは、8ビットのバイナリデータで、各ビットが0か1の値を持っている場合です。 例として、バイト0x6B(バイナリで 01101011)を考えます。

  1. 特定のビットを取得する例 ビットマスクを使用して、特定のビットの値を取得することができます。 例えば、3番目のビット(最下位ビットを0番目と数えた場合)の値を取得する場合を考えてみましょう(AND演算)。
plaintext
バイト:  01101011
マスク:  00000100 (1を左に3ビットシフト)
-----------------
結果:    00000000 (0番目から3番目のビットは0)

結果として、3番目のビットの値は0になります。

  1. 特定のビットを設定する例 ビットマスクを使用して、特定のビットを1に設定することもできます。 例えば、5番目のビットを1に設定する場合を考えてみましょう(OR演算)。
plaintext
バイト:  01101011
マスク:  00100000 (1を左に5ビットシフト)
-----------------
結果:    01101011 (5番目のビットが1に設定されました)

結果として、5番目のビットが1に設定されました。

  1. 特定のビットをクリアする例 ビットマスクを使用して、特定のビットを0にクリアすることもできます。 例えば、2番目のビットを0にクリアする場合を考えてみましょう(AND演算)。
plaintext
バイト:  01101011
マスク:  11111011 (0を左に2ビットシフトし、反転)
-----------------
結果:    01101011 (2番目のビットは変更されずに残りました)

結果として、2番目のビットは変更されずに残りました。

このように、ビットマスクは特定のビット操作を行う際に便利なテクニックです。 ビット演算を使用して特定のビットの値を取得、設定、クリアすることができ、プログラミングにおいてさまざまな用途に利用されます。


V3_SWAP_EXACT_IN

V3_SWAP_EXACT_IN
solidity
uint256 constant V3_SWAP_EXACT_IN = 0x00;

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


V3_SWAP_EXACT_OUT

V3_SWAP_EXACT_OUT
solidity
uint256 constant V3_SWAP_EXACT_OUT = 0x01;

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


PERMIT2_TRANSFER_FROM

PERMIT2_TRANSFER_FROM
solidity
uint256 constant PERMIT2_TRANSFER_FROM = 0x02;

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


PERMIT2_PERMIT_BATCH

PERMIT2_PERMIT_BATCH
solidity
uint256 constant PERMIT2_PERMIT_BATCH = 0x03;

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


SWEEP

SWEEP
solidity
uint256 constant SWEEP = 0x04;

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


TRANSFER

TRANSFER
solidity
uint256 constant TRANSFER = 0x05;

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


PAY_PORTION

PAY_PORTION
solidity
uint256 constant PAY_PORTION = 0x06;

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


FIRST_IF_BOUNDARY

FIRST_IF_BOUNDARY
solidity
uint256 constant FIRST_IF_BOUNDARY = 0x08;

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

コマンドの実行を効率的に行うために、ネストされた条件分岐(if文)のブロック内でコマンドを分割する境界を定義しています。 この定数がどのように使用されるかを詳しく説明します。

  1. FIRST_IF_BOUNDARYの値は0x08です。 16進数表記で、これは00001000のバイナリ表現に相当します。

  2. コマンドは、実行するための種類や条件に応じて複数のタイプに分類されます。 コマンドタイプは0x00から0x3fの範囲にあります。

  3. コマンドタイプがFIRST_IF_BOUNDARYより小さい(0から7まで)場合、最初のネストされた条件分岐のブロック内でコマンドが実行されます。 これにより、コマンドの種類によって最初の条件分岐が効率的に実行されることが保証されます。

  4. コマンドタイプがFIRST_IF_BOUNDARYより大きい(8以上)場合、最初の条件分岐のブロックでは実行されず、さらに深くネストされた条件分岐のブロック内でコマンドが実行されます。 これにより、異なるタイプのコマンドを適切にグループ化して処理することができます。

このようにFIRST_IF_BOUNDARYは、コマンドの種類に基づいてネストされた条件分岐のブロック内でコマンドを分割する目印のようなものです。 これにより、異なる種類のコマンドを適切な場所で実行し、処理を効率的に行うことができます。


V2_SWAP_EXACT_IN

V2_SWAP_EXACT_IN
solidity
uint256 constant V2_SWAP_EXACT_IN = 0x08;

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


V2_SWAP_EXACT_OUT

V2_SWAP_EXACT_OUT
solidity
uint256 constant V2_SWAP_EXACT_OUT = 0x09;

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


PERMIT2_PERMIT

PERMIT2_PERMIT
solidity
uint256 constant PERMIT2_PERMIT = 0x0a;

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


WRAP_ETH

WRAP_ETH
solidity
uint256 constant WRAP_ETH = 0x0b;

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


UNWRAP_WETH

UNWRAP_WETH
solidity
uint256 constant UNWRAP_WETH = 0x0c;

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


PERMIT2_TRANSFER_FROM_BATCH

PERMIT2_TRANSFER_FROM_BATCH
solidity
uint256 constant PERMIT2_TRANSFER_FROM_BATCH = 0x0d;

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


BALANCE_CHECK_ERC20

BALANCE_CHECK_ERC20
solidity
uint256 constant BALANCE_CHECK_ERC20 = 0x0e;

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


SECOND_IF_BOUNDARY

SECOND_IF_BOUNDARY
solidity
uint256 constant SECOND_IF_BOUNDARY = 0x10;

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


SEAPORT_V1_5

SEAPORT_V1_5
solidity
uint256 constant SEAPORT_V1_5 = 0x10;

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

SeaPort NFTマーケットプレイスのOpenseaが提供する売買プロトコルのことです。


LOOKS_RARE_V2

LOOKS_RARE_V2
solidity
uint256 constant LOOKS_RARE_V2 = 0x11;

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


NFTX

NFTX
solidity
uint256 constant NFTX = 0x12;

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


CRYPTOPUNKS

CRYPTOPUNKS
solidity
uint256 constant CRYPTOPUNKS = 0x13;

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


OWNER_CHECK_721

OWNER_CHECK_721
solidity
uint256 constant OWNER_CHECK_721 = 0x15;

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


OWNER_CHECK_1155

OWNER_CHECK_1155
solidity
uint256 constant OWNER_CHECK_1155 = 0x16;

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


THIRD_IF_BOUNDARY

THIRD_IF_BOUNDARY
solidity
uint256 constant THIRD_IF_BOUNDARY = 0x18;

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


X2Y2_721

X2Y2_721
solidity
uint256 constant X2Y2_721 = 0x18;

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


SUDOSWAP

SUDOSWAP
solidity
uint256 constant SUDOSWAP = 0x19;

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


NFT20

NFT20
solidity
uint256 constant NFT20 = 0x1a;

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


X2Y2_1155

X2Y2_1155
solidity
uint256 constant X2Y2_1155 = 0x1b;

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


FOUNDATION

FOUNDATION
solidity
uint256 constant FOUNDATION = 0x1c;

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


SWEEP_ERC1155

SWEEP_ERC1155
solidity
uint256 constant SWEEP_ERC1155 = 0x1d;

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


ELEMENT_MARKET

ELEMENT_MARKET
solidity
uint256 constant ELEMENT_MARKET = 0x1e;

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


FOURTH_IF_BOUNDARY

FOURTH_IF_BOUNDARY
solidity
uint256 constant FOURTH_IF_BOUNDARY = 0x20;

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


SEAPORT_V1_4

SEAPORT_V1_4
solidity
uint256 constant SEAPORT_V1_4 = 0x20;

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


EXECUTE_SUB_PLAN

EXECUTE_SUB_PLAN
solidity
uint256 constant EXECUTE_SUB_PLAN = 0x21;

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


APPROVE_ERC20

APPROVE_ERC20
solidity
uint256 constant APPROVE_ERC20 = 0x22;

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


Constants

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

CONTRACT_BALANCE

詳細
solidity
uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000;

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

この定数は、特定のトークンの残高を入力として使用する場合に、そのトークンの残高をこのコントラクトのバランスとして使用することを示すために使用されます。

この定数は、バイナリ表記で1が最も重要なビットに位置している値です。 この1は、「バイナリ表記において桁の値が最も大きなビット」として知られるビットで、他のビットよりも桁の値が大きくなります。 このビットが 1 であることは、特定の条件が成立していることを示します。

この定数の値 0x8000000000000000000000000000000000000000000000000000000000000000は、16進数表記で8000000000000000000000000000000000000000000000000000000000000000です。これは、バイナリ表記で10000000000000000000000000000000000000000000000000000000000000000です。

この値をビットで表すと、ビットが1であり、他のビットはすべて0です。 この特性を利用して、トークンの残高を入力として使用する際に、このビットが1であることを確認することができます。


ALREADY_PAID

詳細
solidity
uint256 internal constant ALREADY_PAID = 0;

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


ETH

詳細
solidity
address internal constant ETH = address(0);

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


MSG_SENDER

詳細
solidity
address internal constant MSG_SENDER = address(1);

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


ADDRESS_THIS

詳細
solidity
address internal constant ADDRESS_THIS = address(2);

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


ADDR_SIZE

詳細
solidity
uint256 internal constant ADDR_SIZE = 20;

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


V3_FEE_SIZE

詳細
solidity
uint256 internal constant V3_FEE_SIZE = 3;

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


NEXT_V3_POOL_OFFSET

詳細
solidity
uint256 internal constant NEXT_V3_POOL_OFFSET = ADDR_SIZE + V3_FEE_SIZE;

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


V3_POP_OFFSET

詳細
solidity
uint256 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE;

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


MULTIPLE_V3_POOLS_MIN_LENGTH

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