Optimismの標準ブリッジコントラクトを紹介します
Optimism(opens in a new tab)は、Optimisitc ロールアップを行うメカニズムのひとつです。 Optimistic ロールアップでは、ネットワークに含まれるすべてノードではなく一部のノードのみを対象としてトランザクションが処理されるため、イーサリアム・メインネット(「レイヤー1」または「L1」とも呼ばれます)よりも手数料が低くなります。 一部のノードのみを対象として処理されるものの、すべてのデータはL1に書き込まれるため、あらゆる事項につき、メインネットにおける完全性および可用性についての保証に基づいて証明、再構築することが可能です。
Optimism(またはその他のL2)上でL1のアセットを使用するには、当該アセットをブリッジする必要があります。 アセットをブリッジする方法のひとつとして、アセット(最も一般的なのは、ETHやERC-20 トークンです)をL1上でロックし、L2上で同等のアセットを受け取る方法があります。 最終的に、これらのアセットを所持するユーザーは、再度L1にブリッジする必要があるでしょう。 L1にアセットをブリッジすると、L2上のアセットはバーンされ、L1上のアセットがユーザーに戻されます。
以上が、Optimismにおける標準ブリッジ(opens in a new tab)の仕組みです。 この記事では、このブリッジ機能についてSolidity上で適切に作成したソースコードを確認しながら、その仕組みを学びます。
制御フロー
ブリッジは、2つのメインフローで構成されます:
- L1からL2への入金
- L2からL1への出金
入金フロー
L1
- ERC-20を入金する場合、入金者はブリッジに対し、入金額を使用するためのアローワンスを与えます。
- 入金者は、L1ブリッジ(
depositERC20
、depositERC20To
、depositETH
あるいはdepositETHTo
)を呼び出します。 - L1ブリッジが、ブリッジされたアセットを保持します。
- ETHの場合:アセットは、呼び出しを通じて入金者に送信されます。
- ERC-20トークンの場合:アセットは、入金者が提供するアローワンスを使用して、ブリッジ自体に送信されます。
- L1のブリッジが、クロスドメインのメッセージメカニズムを通じて、L2のブリッジ上で
finalizeDeposit
を呼び出します。
L2
- L2のブリッジは、
finalizeDeposit
の呼び出しにつき、以下が適切であることを確認します:- クロスドメインのメッセージ・コントラクトからの呼び出しであること。
- ブリッジがL1上で作成されたものであること。
- L2のブリッジはさらに、L2上のERC-20トークンコントラクトにつき、以下が適切であることを確認します:
- L2のコントラクトにおいて、L1における対応するコントラクトが、L1上で送信されたトークンと同一であると報告していること。
- L2のコントラクトが、(ERC-165を使用した(opens in a new tab))適切なインターフェイスをサポートすると報告していること。
- L2のコントラクトが適切であると確認できた場合は、適切なアドレスに対して希望する量のトークンをミントするために、そのコントラクトを呼び出してください。 そうでない場合は、出金プロセスを開始して、ユーザーがL1上のトークンを請求できるようにします。
出金フロー
レイヤー2
- 出金者は、L2のブリッジ(
withdraw
またはwithdrawTo
)を呼び出します 。 - L2のブリッジは、
msg.sender
が所有する適切な数のトークンをバーンします。 - L2のブリッジは、クロスドメインのメッセージ・メカニズムを利用して、L1のブリッジ上で、
finalizeETHWithdrawal
またはfinalizeERC20Withdrawal
を呼び出します。
L1
- L1のブリッジは、
finalizeETHWithdraw
またはfinalizeERC20Withdral
の呼び出しにつき、以下が適切であるかを確認します:- クロスドメインのメッセージ・メカニズムを経由していること。
- L2のブリッジで作成されていること。
- L1のブリッジは、適切な資産(ETHまたはERC-20)を適切なアドレスに送信します。
レイヤー1のコード
以下は、イーサリアム・メインネット(L1)で実行されるコードです。
IL1ERC20Bridge
このインターフェイスは、こちら(opens in a new tab)で定義されています。 このインターフェイスには、ERC-20トークンをブリッジするために必要な機能と定義が含まれます。
1// SPDX-License-Identifier: MITコピー
Optimismのコードの大部分は、MITライセンス(opens in a new tab)に基づいています。
1pragma solidity >0.5.0 <0.9.0;コピー
本記事の執筆時点で、Solidityの最新バージョンは0.8.12です。 バージョン0.9.0においてこのコードが利用できるかは、同バージョンがリリースされるまで不明です。
1/**2 * @title IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Events *7 **********/89 event ERC20DepositInitiated(すべて表示コピー
Optimismのブリッジ関連用語において、入金とは、L1からL2に送金することを意味し、出金とは、L2からL1へ送金することを意味します。
1 address indexed _l1Token,2 address indexed _l2Token,コピー
ほとんどの場合、L1上のERC-20のアドレスは、L2上で対応するERC-20のアドレスとは異なります。 トークンアドレスのリストは、こちら(opens in a new tab)を参照してください。 chainId
が1のアドレスであれば、L1 (メインネット) 上のトークンであり、chainId
が10のアドレスでれば、L2(Optimism)上のトークンです。 残りの2つのchainId
の値は、Kovanテストネットワーク(42)とOptimistic Kovanテストネットワーク(69)のためのものです。
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );コピー
転送にはメモを追加することができ、メモは転送を報告するイベントに追加されます。
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );コピー
入金と出金の両方向につき、同じブリッジのコントラクトが処理します。 つまり、L1のブリッジでは入金を初期化し、出金を確定します。
12 /********************3 * Public Functions *4 ********************/56 /**7 * @dev get the address of the corresponding L2 bridge contract.8 * @return Address of the corresponding L2 bridge contract.9 */10 function l2TokenBridge() external returns (address);すべて表示コピー
実際には、L2のブリッジは事前にデプロイされており、常に「0x42000000000000000000000000000000000000000010
」のアドレスとなるため、この機能は必要ではありません。 この機能は、L2上のブリッジとの対称性を確保するためのものです。というのも、L1ブリッジのアドレスを確認することは無意味とは言えないためです。
1 /**2 * @dev deposit an amount of the ERC20 to the caller's balance on L2.3 * @param _l1Token Address of the L1 ERC20 we are depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @param _amount Amount of the ERC20 to deposit6 * @param _l2Gas Gas limit required to complete the deposit on L2.7 * @param _data Optional data to forward to L2. This data is provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * length, these contracts provide no guarantees about its content.10 */11 function depositERC20(12 address _l1Token,13 address _l2Token,14 uint256 _amount,15 uint32 _l2Gas,16 bytes calldata _data17 ) external;すべて表示コピー
_l2Gas
のパラメータは、このトランザクションが使用できるL2上のガス量です。 この値は、一定の(高い)上限まで無料であるため(opens in a new tab)、ミント時にERC-20がコントラクトが特に異常な動作を行わない限り、問題は発生しません。 以下の関数は、異なるブロックチェーンにおける同一アドレスにアセットをブリッジしたいという一般的なシナリオで用いることができます。
1 /**2 * @dev deposit an amount of ERC20 to a recipient's balance on L2.3 * @param _l1Token Address of the L1 ERC20 we are depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @param _to L2 address to credit the withdrawal to.6 * @param _amount Amount of the ERC20 to deposit.7 * @param _l2Gas Gas limit required to complete the deposit on L2.8 * @param _data Optional data to forward to L2. This data is provided9 * solely as a convenience for external contracts. Aside from enforcing a maximum10 * length, these contracts provide no guarantees about its content.11 */12 function depositERC20To(13 address _l1Token,14 address _l2Token,15 address _to,16 uint256 _amount,17 uint32 _l2Gas,18 bytes calldata _data19 ) external;すべて表示コピー
次のコードはdepositERC20
とほぼ同一の関数ですが、ERC-20トークンを異なるアドレスに送信することができます。
1 /*************************2 * Cross-chain Functions *3 *************************/45 /**6 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the7 * L1 ERC20 token.8 * This call will fail if the initialized withdrawal from L2 has not been finalized.9 *10 * @param _l1Token Address of L1 token to finalizeWithdrawal for.11 * @param _l2Token Address of L2 token where withdrawal was initiated.12 * @param _from L2 address initiating the transfer.13 * @param _to L1 address to credit the withdrawal to.14 * @param _amount Amount of the ERC20 to deposit.15 * @param _data Data provided by the sender on L2. This data is provided16 * solely as a convenience for external contracts. Aside from enforcing a maximum17 * length, these contracts provide no guarantees about its content.18 */19 function finalizeERC20Withdrawal(20 address _l1Token,21 address _l2Token,22 address _from,23 address _to,24 uint256 _amount,25 bytes calldata _data26 ) external;27}すべて表示コピー
Optimismにおける出金(および、他のL2からL1へのメッセージ送信)は、2つのステップで実行されます:
- L2でトランザクションを開始する。
- L1で、トランザクションを確定/クレームする。 L1でのトランザクションは、L2でのトランザクションに対する異議申し立て期間(opens in a new tab) が終了した後で実行可能になります。
IL1StandardBridge
このインターフェイスは、こちら(opens in a new tab)で定義されています。 このブリッジには、ETHを対象とするイベントおよび関数の定義が含まれています。 これらの定義は、ERC-20トークンを対象とするIL1ERC20Bridge
の定義とほぼ同一です。
一部のERC-20トークンは、標準ブリッジでは処理できず、カスタム処理が必要となるため、このブリッジのインターフェイスは2つのファイルで構成されています。 これにより、カスタム処理が必要なトークンを取り扱うブリッジについてはIL1ERC20Bridge
を実装すればよく、ETHのブリッジ機能を実装する必要はありません。
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34import "./IL1ERC20Bridge.sol";56/**7 * @title IL1StandardBridge8 */9interface IL1StandardBridge is IL1ERC20Bridge {10 /**********11 * Events *12 **********/13 event ETHDepositInitiated(14 address indexed _from,15 address indexed _to,16 uint256 _amount,17 bytes _data18 );すべて表示コピー
このイベントは、ERC-20トークン用のイベント(ERC20DepositInitiated
)とほぼ同一ですが、L1およびL2上のトークンアドレスが含まれていない点が異なります。 他のイベントおよび関数についても、この点が異なります。
1 event ETHWithdrawalFinalized(2 .3 。4 。5 );67 /********************8 * Public Functions *9 ********************/1011 /**12 * @dev Deposit an amount of the ETH to the caller's balance on L2.13 。14 。15 。16 */17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;1819 /**20 * @dev Deposit an amount of ETH to a recipient's balance on L2.21 。22 。23 。24 */25 function depositETHTo(26 address _to,27 uint32 _l2Gas,28 bytes calldata _data29 ) external payable;3031 /*************************32 * Cross-chain Functions *33 *************************/3435 /**36 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the37 * L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called38 * before the withdrawal is finalized.39 。40 。41 。42 */43 function finalizeETHWithdrawal(44 address _from,45 address _to,46 uint256 _amount,47 bytes calldata _data48 ) external;49}すべて表示コピー
CrossDomainEnabled
このコントラクト(opens in a new tab)は、L1およびL2の両方のブリッジにおいて継承され、相手のレイヤーに対してメッセージを送信します。
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* Interface Imports */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";コピー
このインターフェース(opens in a new tab)は、クロスドメインのメッセンジャーを用いて、相手のレイヤーに対するメッセージの送信方法をコントラクトに通知するものです。 このクロスドメインのメッセンジャーは完全に別個のシステムであるため、今後改めて記事を執筆したいと考えています。
1/**2 * @title CrossDomainEnabled3 * @dev Helper contract for contracts performing cross-domain communications4 *5 * Compiler used: defined by inheriting contract6 */7contract CrossDomainEnabled {8 /*************9 * Variables *10 *************/1112 // Messenger contract used to send and receive messages from the other domain.13 address public messenger;1415 /***************16 * Constructor *17 ***************/1819 /**20 * @param _messenger Address of the CrossDomainMessenger on the current layer.21 */22 constructor(address _messenger) {23 messenger = _messenger;24 }すべて表示コピー
このコントラクトが知る必要がある唯一のパラメータは、当該レイヤーにおけるクロスドメイン・メッセンジャーのアドレスです。 このパラメータは、コンストラクタ上で設定された後は変更されません。
12 /**********************3 * Function Modifiers *4 **********************/56 /**7 * Enforces that the modified function is only callable by a specific cross-domain account.8 * @param _sourceDomainAccount The only account on the originating domain which is9 * authenticated to call this function.10 */11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {すべて表示コピー
クロスドメインのメッセージング機能は、ブロックチェーン(イーサリアム・メインネットまたはOptimism)上で実行されているあらゆるコントラクトからアクセス可能です。 ただし、相手方のブリッジから送信された特定のメッセージのみを信頼するためには、双方のブリッジが必要になります。
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: messenger contract unauthenticated"4 );コピー
適切なクロスドメイン・メッセンジャー (以下のmessenger
を参照)から送信されたメッセージのみ、信頼できます。
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: wrong sender of cross-domain message"5 );コピー
クロスドメイン・メッセンジャーでは、the .xDomainMessageSender()
関数(opens in a new tab)を用いて、相手方のレイヤーにメッセージを送信するアドレスを提供します。 当該メッセージで開始されたトランザクションで呼び出す場合に限り、メッセージの送信元アドレスを表示することができます。
このメッセージにつき、相手方のブリッジから送信されたものであることを確認する必要があります。
12 _;3 }45 /**********************6 * Internal Functions *7 **********************/89 /**10 * Gets the messenger, usually from storage. This function is exposed in case a child contract11 * needs to override.12 * @return The address of the cross-domain messenger contract which should be used.13 */14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {15 return ICrossDomainMessenger(messenger);16 }すべて表示コピー
この機能は、クロスドメイン・メッセンジャーを返します。 messenger
の変数ではなく関数を使うのは、この値を継承するコントラクトに対し、どのクロスドメイン・メッセンジャーを使用するかを特定するアルゴリズムを使用できるようにするためです。
12 /**3 * Sends a message to an account on another domain4 * @param _crossDomainTarget The intended recipient on the destination domain5 * @param _message The data to send to the target (usually calldata to a function with6 * `onlyFromCrossDomainAccount()`)7 * @param _gasLimit The gasLimit for the receipt of the message on the target domain.8 */9 function sendCrossDomainMessage(10 address _crossDomainTarget,11 uint32 _gasLimit,12 bytes memory _messageすべて表示コピー
最後に、次の関数は相手方のレイヤーにメッセージを送信するものです。
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignコピー
Slither(opens in a new tab)は、Optimismにおいて、すべてのコントラクトの脆弱性やその他の潜在的な問題箇所を特定するために実行される静的解析ツールです。 ここでは、以下の行により2つの脆弱性がトリガーされます。
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);2 }3}コピー
このケースでは、Slitherが当該情報を把握する方法を持たない場合でも、getCrossDomainMessenger()
が信頼できるアドレスを返すと分かっているため、リエントランシーについて心配する必要はありません。
L1上のブリッジコントラクト
このコントラクトのソースコードは、こちら(opens in a new tab)で入手してください。
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;コピー
このインターフェイスは、他のコントラクトにも含まれる場合があるため、Solidityのさまざまなバージョンをサポートする必要があります。 しかしここでは、ブリッジ自体がコントラクトであるため、使用できるSolidityのバージョンを限定することが可能です。
1/* Interface Imports */2import { IL1StandardBridge } from "./IL1StandardBridge.sol";3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";コピー
IL1ERC20BridgeとIL1StandardBridgeについては、すでに説明しました。
1import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";コピー
このインターフェイス(opens in a new tab)では、L2上で標準ブリッジを制御するためのメッセージを作成します。
1import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";コピー
このインターフェイス(opens in a new tab)は、ERC-20のコントラクトを制御するために使用します。 詳細については、こちらをご覧ください。
1/* Library Imports */2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";コピー
上記で説明したように、このコントラクトはレイヤー間のメッセージングに使用します。
1import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";コピー
Lib_PredeployAddresses
(opens in a new tab)には、常に同じアドレスを持つL2上のコントラクトのアドレスが含まれています。 これには、L2上の標準ブリッジが含まれます。
1import { Address } from "@openzeppelin/contracts/utils/Address.sol";コピー
OpenZeppelinのアドレス・ユーティリティ(opens in a new tab)です。 これは、コントラクト上のアドレスと外部所有アカウント(EOA)に含まれるアドレスを区別するために使われます。
これは、直接の呼び出しとコントラクトのコンストラクタで作成した呼び出しを区別できないため完全なソリューションとは言えませんが、少なくとも、よくあるユーザーエラーを特定、防止することは可能です。
1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";コピー
ERC-20標準(opens in a new tab)では、コントラクトが実行失敗を報告する手段として以下の2つがあります:
- 元に戻す
false
を返す
両方のケースに対応するとコードがより複雑になるため、代わりにOpenZeppelinのSafeERC20
(opens in a new tab)を使用します。これにより、失敗した場合は常に元に戻されます(opens in a new tab)。
1/**2 * @title L1StandardBridge3 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard4 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits5 * and listening to it for newly finalized withdrawals.6 *7 */8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {9 using SafeERC20 for IERC20;すべて表示コピー
この行は、IERC20
インターフェイスを使用する際に、常にIERC20
ラッパーを用いることを指定するものです。
12 /********************************3 * External Contract References *4 ********************************/56 address public l2TokenBridge;コピー
L2StandardBridgeのアドレスです。
12 // Maps L1 token to L2 token to balance of the L1 token deposited3 mapping(address => mapping(address => uint256)) public deposits;コピー
2次元スパース配列(opens in a new tab)を定義するには、このようなダブルマッピング(opens in a new tab)を用います。 このデータ構造の値は、deposit[L1 token addr][L2 token addr]
として識別されます。 初期値はゼロになります。 ゼロ以外の値が設定されたセルのみが、ストレージに書き込まれます。
12 /***************3 * Constructor *4 ***************/56 // This contract lives behind a proxy, so the constructor parameters will go unused.7 constructor() CrossDomainEnabled(address(0)) {}コピー
ストレージ内のすべての変数をコピーせずに、このコントラクトを更新するには、 プロキシ
(opens in a new tab)を使用します。プロキシは、delegatecall
(opens in a new tab)を用いて、プロキシのコントラクトにおいてアドレスが保存された別個のコントラクトに対して呼び出しを転送するものです(コントラクトを更新する際に、プロキシに対してこのアドレスを変更するように指示することになります)。 「delegatecall
」を使用すると、ストレージは、呼び出し元のコントラクトのストレージのままになるため、すべてのコントラクト状態変数の値は影響を受けません。
このパターンを用いる効果のひとつとして、delegatecall
の呼び出し先であるコントラクトのストレージが使用されないため、送信されたコンストラクタの値が意味を持たない点が挙げられます。 これこそ、CrossDomainEnabled
のコンストラクタに対して無意味な値を入力できる理由です。 同時に、以下の初期化がコンストラクタとは別個に存在するである理由でもあります。
1 /******************2 * Initialization *3 ******************/45 /**6 * @param _l1messenger L1 Messenger address being used for cross-chain communications.7 * @param _l2TokenBridge L2 standard bridge address.8 */9 // slither-disable-next-line external-functionすべて表示コピー
このSlitherのテスト(opens in a new tab)は、コントラクトのコードにより呼び出される関数以外の関数であり、public
ではなく、external
と宣言される関数を特定するものです。 external
関数のガス代は、コールデータに含まれるパラメータで指定できるため、安価に抑えることができます。 public
と宣言された関数は、コントラクト内でアクセス可能である必要があります。 コントラクトはそれ自体のコールデータを変更できないため、パラメータはメモリに保存する必要があります。 このような関数を外部から呼び出す場合、コールデータをメモリにコピーする必要があるため、ガス代が発生します。 今回は関数を1回のみ呼び出すため、コスト上の非効率性は度外視できます。
1 function initialize(address _l1messenger, address _l2TokenBridge) public {2 require(messenger == address(0), "Contract has already been initialized.");コピー
initialize
関数は、1回だけ呼び出す必要があります。 L1のクロスドメイン・メッセンジャーまたは L2のトークンブリッジのいずれかのアドレスが変更されると、新しいプロキシとそれを呼び出す新しいブリッジが作成されます。 これは、システム全体がアップグレードされた場合を除いて発生する可能性は低く、非常にまれです。
この関数には、呼び出し可能なアカウントを制限するメカニズムが含まれない点に注意してください。 つまり理論的には、ネットワークに対する攻撃者は、プロキシとブリッジの最初のバージョンがデプロイされるまで待機し、正当なユーザーがinitialize
関数にアクセスできる前にフロントラン(opens in a new tab)を実行することが可能です。 これを防ぐには、以下の2つの方法があります:
- コントラクトがEOAにより直接デプロイされるのではなく、別のコントラクトが作成したトランザクションにおいて(opens in a new tab)デプロイされる場合、全体のプロセスがアトミックになり、他のトランザクションが実行される前に終了する場合があります。
- 正当な
initialize
呼び出しが失敗した場合、常に、新たに作成されたプロキシおよびブリッジを無視し、さらに新しいものを作成することが可能です。
1 messenger = _l1messenger;2 l2TokenBridge = _l2TokenBridge;3 }コピー
これらは、ブリッジが知る必要がある2つのパラメータです。
12 /**************3 * Depositing *4 **************/56 /** @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious7 * contract via initcode, but it takes care of the user error we want to avoid.8 */9 modifier onlyEOA() {10 // Used to stop deposits from contracts (avoid accidentally lost tokens)11 require(!Address.isContract(msg.sender), "Account not EOA");12 _;13 }すべて表示コピー
これは、OpenZeppelinのAddress
ユーティリティが必要となる理由です。
1 /**2 * @dev This function can be called with no data3 * to deposit an amount of ETH to the caller's balance on L2.4 * Since the receive function doesn't take data, a conservative5 * default amount is forwarded to L2.6 */7 receive() external payable onlyEOA {8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));9 }すべて表示コピー
この関数は、テスト用のものです。 通常の使用を目的とするものではないため、インターフェイスの定義には含まれない点に注意してください。
1 /**2 * @inheritdoc IL1StandardBridge3 */4 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {5 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);6 }78 /**9 * @inheritdoc IL1StandardBridge10 */11 function depositETHTo(12 address _to,13 uint32 _l2Gas,14 bytes calldata _data15 ) external payable {16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);17 }すべて表示コピー
これら2つの関数は、実際のETH入金を処理する関数である_initiateETHDeposit
に関連したラッパーです。
1 /**2 * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of3 * the deposit.4 * @param _from Account to pull the deposit from on L1.5 * @param _to Account to give the deposit to on L2.6 * @param _l2Gas Gas limit required to complete the deposit on L2.7 * @param _data Optional data to forward to L2. This data is provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * length, these contracts provide no guarantees about its content.10 */11 function _initiateETHDeposit(12 address _from,13 address _to,14 uint32 _l2Gas,15 bytes memory _data16 ) internal {17 // Construct calldata for finalizeDeposit call18 bytes memory message = abi.encodeWithSelector(すべて表示コピー
クロスドメインのメッセージは、メッセージをコールデータとして宛先のコントラクトを呼び出す仕組みとして機能します。 Solidityのコントラクトは常に、コールデータをABI仕様(opens in a new tab)に従って解釈します。 Solidityのabi.encodeWithSelector
(opens in a new tab)は、このコールデータを作成するものです。
1 IL2ERC20Bridge.finalizeDeposit.selector,2 address(0),3 Lib_PredeployAddresses.OVM_ETH,4 _from,5 _to,6 msg.value,7 _data8 );コピー
ここでのメッセージは、 finalizeDeposit
関数(opens in a new tab)を以下のパラメータで呼び出すことです。
パラメータ | 値 | 説明 |
---|---|---|
_l1Token | address(0) | L1上のETH (ERC-20トークンではない) を表す特別な値 |
_l2Token | Lib_PredeployAddresses.OVM_ETH | Optimism上でETHを管理するL2のコントラクト0xDeadDeAddeAddEAddeadDEaDDeaDDeAD0000 (このコントラクトは、Optimism内部でのみ使用されます) |
_from | _from | L1上のETH送信元アドレス |
_to | _to | L2上のETH受領用アドレス |
amount | msg.value | 送信されたwei額(すでにブリッジに送信済みの額) |
_data | _data | 入金に添付する追加の日付 |
1 // Send calldata into L22 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);コピー
クロスドメイン・メッセンジャーを通じて、メッセージを送信します。
1 // slither-disable-next-line reentrancy-events2 emit ETHDepositInitiated(_from, _to, msg.value, _data);3 }コピー
この送信をリッスンするすべての分散型アプリケーションに対し、通知イベントを発行します。
1 /**2 * @inheritdoc IL1ERC20Bridge3 */4 function depositERC20(5 .6 。7 。8 ) external virtual onlyEOA {9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);10 }1112 /**13 * @inheritdoc IL1ERC20Bridge14 */15 function depositERC20To(16 .17 。18 。19 ) external virtual {20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);21 }すべて表示コピー
これら2つの関数は、実際のERC-20トークンの入金を処理する関数である_initiateERC20Deposit
に関連したラッパーです。
1 /**2 * @dev Performs the logic for deposits by informing the L2 Deposited Token3 * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)4 *5 * @param _l1Token Address of the L1 ERC20 we are depositing6 * @param _l2Token Address of the L1 respective L2 ERC207 * @param _from Account to pull the deposit from on L18 * @param _to Account to give the deposit to on L29 * @param _amount Amount of the ERC20 to deposit.10 * @param _l2Gas Gas limit required to complete the deposit on L2.11 * @param _data Optional data to forward to L2. This data is provided12 * solely as a convenience for external contracts. Aside from enforcing a maximum13 * length, these contracts provide no guarantees about its content.14 */15 function _initiateERC20Deposit(16 address _l1Token,17 address _l2Token,18 address _from,19 address _to,20 uint256 _amount,21 uint32 _l2Gas,22 bytes calldata _data23 ) internal {すべて表示コピー
この関数は、上記の_initiateETHDeposit
に似ていますが、いくつかの重要な点が異なります。 最大の違いは、この関数では、トークンのアドレスおよび送信額をパラメータとして受け取るという点です。 ETHの場合、ブリッジへの呼び出しにはすでに、ブリッジアカウントへの資産の移転(msg.value
)が含まれています。
1 // When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future2 // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if3 // _from is an EOA or address(0).4 // slither-disable-next-line reentrancy-events, reentrancy-benign5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);コピー
ERC-20トークンについては、ETHの場合とは異なる送信プロセスが実行されます:
- ユーザー(
_from
)は、ブリッジに対し、送信したいトークン量に見合ったアローワンスを与えます。 - 次に、トークンコントラクトのアドレスや金額等で、ブリッジを呼び出します。
- ブリッジは、入金プロセスの一環として、トークンをブリッジ自体に送信します。
最初のステップは、第2、3のステップとは別のトランザクションで実行しても構いません。 ただし、_initiateERC20Deposit
を呼び出す 2 つの関数 (depositERC20
とdepositERC20To
)は、_from
をパラメータとしてmsg.sender
を含むこの関数を呼び出すだけなので、フロントランニングの問題は発生しません。
1 // Construct calldata for _l2Token.finalizeDeposit(_to, _amount)2 bytes memory message = abi.encodeWithSelector(3 IL2ERC20Bridge.finalizeDeposit.selector,4 _l1Token,5 _l2Token,6 _from,7 _to,8 _amount,9 _data10 );1112 // Send calldata into L213 // slither-disable-next-line reentrancy-events, reentrancy-benign14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);1516 // slither-disable-next-line reentrancy-benign17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;すべて表示コピー
deposits
のデータ構造に、トークンの入金額を追加します。 L2上には、L1上にある同一のERC-20トークンに対応した複数のアドレスが存在する場合があるため、入金の推移を追跡するには、L1上のERC-20トークンに関するブリッジ上の残高を参照するだけでは不十分です。
12 // slither-disable-next-line reentrancy-events3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);4 }56 /*************************7 * Cross-chain Functions *8 *************************/910 /**11 * @inheritdoc IL1StandardBridge12 */13 function finalizeETHWithdrawal(14 address _from,15 address _to,16 uint256 _amount,17 bytes calldata _dataすべて表示コピー
L2のブリッジは、L2のクロスドメイン・メッセンジャーに対して、L1のクロスドメイン・メッセンジャーがこの関数を呼び出すことを指示するメッセージを送信します(もちろん、L1上で、このメッセージを確定するトランザクション(opens in a new tab)が送信された後においてです)。
1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {コピー
このメッセージにつき、L2のトークン・ブリッジで作成され、クロスドメイン・メッセンジャーを経由して送信された正当なメッセージであることを確認してください。 この関数は、ブリッジからETHを出金するために使用するため、承認された呼び出し元だけが呼び出したことを確認する必要があります。
1 // slither-disable-next-line reentrancy-events2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));コピー
ETHを送信するには、msg.value
でwei金額を指定して、受領者を呼び出します。
1 require(success, "TransferHelper::safeTransferETH: ETH transfer failed");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);コピー
出金イベントを発行します。
1 }23 /**4 * @inheritdoc IL1ERC20Bridge5 */6 function finalizeERC20Withdrawal(7 address _l1Token,8 address _l2Token,9 address _from,10 address _to,11 uint256 _amount,12 bytes calldata _data13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {すべて表示コピー
この関数は、上記の finalizeETHWithdrawal
関数に類似していますが、ERC-20トークンに関する事項が異なります。
1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;コピー
deposits
のデータ構造を更新します。
12 // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer3 // slither-disable-next-line reentrancy-events4 IERC20(_l1Token).safeTransfer(_to, _amount);56 // slither-disable-next-line reentrancy-events7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);8 }91011 /*****************************12 * Temporary - Migrating ETH *13 *****************************/1415 /**16 * @dev Adds ETH balance to the account. This is meant to allow for ETH17 * to be migrated from an old gateway to a new gateway.18 * NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the19 * old contract20 */21 function donateETH() external payable {}22}すべて表示コピー
このブリッジは、従来の実装から変更されています。 以前の実装から現在の実装に移行した際に、すべての資産を移転させる必要がありました。 ERC-20トークンについては、移転のみが可能でした。 一方、ETHをコントラクトに移転するには、そのコントラクトの承認が必要であり、これはdonateETH
で実行できます。
L2上のERC-20トークン
ERC-20トークンを標準ブリッジに適合させるためには、標準ブリッジのみがトークンをミントできるようにする必要があります。 これが必要になるのは、各ブリッジにおいて、Optimism上で流通するトークンの数がL1のブリッジコントラクト内でロックされたトークンの数と一致することを保証する必要があるためです。 L2上のトークンが多すぎる場合、L2上のアセットL1にブリッジして戻すことができないユーザーが発生します。 この場合、信頼できるブリッジが存在せず、事実上、部分準備銀行制度(opens in a new tab)を生み出すことになってしまいます。 一方、L1上でのトークンが多くなりすぎると、L2トークンをバーンしない限りトークンをリリースできなくなるため、ブリッジコントラクト内の一部のトークンは永遠にロックされた状態になってしまいます。
IL2StandardERC20
標準ブリッジを使用するL2上のすべてのERC-20トークンは、標準ブリッジが必要とする関数およびイベントが搭載されたこのインターフェイス(opens in a new tab)を提供する必要があります。
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";コピー
ERC-20に対する標準インターフェイス(opens in a new tab)には、mint
およびburn
関数が含まれません。 これらのメソッドは、ERC-20標準(opens in a new tab)において必須でないため、トークンを作成、破壊するメカニズムは指定されていないのです。
1import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";コピー
ERC-165インターフェイス(opens in a new tab)は、コントラクトが提供する機能を特定するために用います。 この標準については、こちらで読むことができます(opens in a new tab)。
1interface IL2StandardERC20 is IERC20, IERC165 {2 function l1Token() external returns (address);コピー
この関数は、このコントラクトに対してブリッジされた L1上のトークンアドレスを提供するものです。 同じ機能の逆方向の関数が存在しない点に注意してください。 L1上のトークンについては、L2のサポートが計画されていたか、あるいはいつ実装されたかを問わず、常にブリッジ可能である必要があります。
12 function mint(address _to, uint256 _amount) external;34 function burn(address _from, uint256 _amount) external;56 event Mint(address indexed _account, uint256 _amount);7 event Burn(address indexed _account, uint256 _amount);8}コピー
トークンをミント (作成) およびバーン (破棄) するための関数とイベントです。 トークン数が適切である(L1上でロックされたトークン数と一致する)ことを保証するため、これらの関数を実行できるのはこのブリッジのみである必要があります。
L2StandardERC20
これは(opens in a new tab)、IL2StandardERC20
インターフェイスの実装です。 カスタムロジックが必要ない場合は、常にこの関数を用いてください。
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";コピー
OpenZeppelinで作成したERC-20コントラクト(opens in a new tab)です。 Optimismでは、既存の機能がよく監査され、資産を保持する上で十分信頼できる場合は、新たな機能を追加すべきではないと考えています。
1import "./IL2StandardERC20.sol";23contract L2StandardERC20 is IL2StandardERC20, ERC20 {4 address public l1Token;5 address public l2Bridge;コピー
これらは、通常ERC-20では必要になりませんが、ここでは追加で必要となる2つの設定パラメータです。
12 /**3 * @param _l2Bridge Address of the L2 standard bridge.4 * @param _l1Token Address of the corresponding L1 token.5 * @param _name ERC20 name.6 * @param _symbol ERC20 symbol.7 */8 constructor(9 address _l2Bridge,10 address _l1Token,11 string memory _name,12 string memory _symbol13 ) ERC20(_name, _symbol) {14 l1Token = _l1Token;15 l2Bridge = _l2Bridge;16 }すべて表示コピー
まず、ERC20(_name, _symbol)
で継承したコントラクトのコンストラクタを呼び出し、新たに変数を設定します。
12 modifier onlyL2Bridge() {3 require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");4 _;5 }678 // slither-disable-next-line external-function9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC16511 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^12 IL2StandardERC20.mint.selector ^13 IL2StandardERC20.burn.selector;14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;15 }すべて表示コピー
ERC-165(opens in a new tab)は、このように動作します。 各インターフェイスは、サポートする一連の関数を含んでおり、これらの機能のexclusive(opens in a new tab)またはABI関数セレクタ(opens in a new tab)として特定されます。
L2のブリッジは、ERC-165を健全性チェックとして用いて、資産を送信するのに用いるERC-20コントラクトが IL2StandardERC20
であるか確認します。
**注意:不正なコントラクトがsupportsInterface
に対して虚偽の値を返すことを防ぐメカニズムは含まれていないため、これはセキュリティ保護のメカニズムではなく、健全性チェックのメカニズムです。
1 // slither-disable-next-line external-function2 function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {3 _mint(_to, _amount);45 emit Mint(_to, _amount);6 }78 // slither-disable-next-line external-function9 function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {10 _burn(_from, _amount);1112 emit Burn(_from, _amount);13 }14}すべて表示コピー
資産をミント/バーンできるのは、L2のブリッジのみです。
_mint
および_burn
は、実際には、OpenZeppelinで作成したERC-20コントラクトで定義されています。 トークンをミント/バーンできる条件は、ERC-20を使用する方法と同じように多種多様であるため、このコントラクトは単純に外部に露出していないのです。
L2ブリッジのコード
これは、Optimismでブリッジを実行するコードです。 このコントラクトのソースコードは、こちら(opens in a new tab)から入手できます。
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34/* Interface Imports */5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";コピー
IL2ERC20Bridge(opens in a new tab)のインターフェースは、上述したL1用のインターフェイスとほぼ同様ですが、 以下の2点が大きく異なります。
- L1では、あなたが入金を開始し、出金を確定します。 L2の場合、あなたは出金を開始し、入金を確定します。
- L1では、ETHとERC-20トークンを区別する必要があります。 L2では、Optimism上のETH残高は内部で、アドレス「0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000(opens in a new tab)」のERC-20トークンとして処理されるため、両方で同じ関数を使います。
1/* Library Imports */2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";56/* Contract Imports */7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";89/**10 * @title L2StandardBridge11 * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to12 * enable ETH and ERC20 transitions between L1 and L2.13 * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard14 * bridge.15 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L116 * bridge to release L1 funds.17 */18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {19 /********************************20 * External Contract References *21 ********************************/2223 address public l1TokenBridge;すべて表示コピー
これは、L1ブリッジのアドレスを追跡する関数です。 L1用のブリッジの場合とは異なり、この変数は必須です。 L1ブリッジのアドレスは、事前に知ることはできません。
12 /***************3 * Constructor *4 ***************/56 /**7 * @param _l2CrossDomainMessenger Cross-domain messenger used by this contract.8 * @param _l1TokenBridge Address of the L1 bridge deployed to the main chain.9 */10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)11 CrossDomainEnabled(_l2CrossDomainMessenger)12 {13 l1TokenBridge = _l1TokenBridge;14 }1516 /***************17 * Withdrawing *18 ***************/1920 /**21 * @inheritdoc IL2ERC20Bridge22 */23 function withdraw(24 address _l2Token,25 uint256 _amount,26 uint32 _l1Gas,27 bytes calldata _data28 ) external virtual {29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);30 }3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function withdrawTo(36 address _l2Token,37 address _to,38 uint256 _amount,39 uint32 _l1Gas,40 bytes calldata _data41 ) external virtual {42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);43 }すべて表示コピー
これら2つの関数は、出金を開始するものです。 L1上のトークンアドレスを指定する必要がない点に注意してください。 L1上で対応するトークンのアドレスは、L2トークンが伝達するからです。
12 /**3 * @dev Performs the logic for withdrawals by burning the token and informing4 * the L1 token Gateway of the withdrawal.5 * @param _l2Token Address of L2 token where withdrawal is initiated.6 * @param _from Account to pull the withdrawal from on L2.7 * @param _to Account to give the withdrawal to on L1.8 * @param _amount Amount of the token to withdraw.9 * @param _l1Gas Unused, but included for potential forward compatibility considerations.10 * @param _data Optional data to forward to L1. This data is provided11 * solely as a convenience for external contracts. Aside from enforcing a maximum12 * length, these contracts provide no guarantees about its content.13 */14 function _initiateWithdrawal(15 address _l2Token,16 address _from,17 address _to,18 uint256 _amount,19 uint32 _l1Gas,20 bytes calldata _data21 ) internal {22 // When a withdrawal is initiated, we burn the withdrawer's funds to prevent subsequent L223 // usage24 // slither-disable-next-line reentrancy-events25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);すべて表示コピー
_from
パラメータに依存するのではなく、msg.sender
を使用する点に注意してください。これにより、偽造がより困難になります(私の知る限り、不可能です)。
12 // Construct calldata for l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)3 // slither-disable-next-line reentrancy-events4 address l1Token = IL2StandardERC20(_l2Token).l1Token();5 bytes memory message;67 if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {コピー
L1では、ETHとERC-20トークンを区別する必要があります。
1 message = abi.encodeWithSelector(2 IL1StandardBridge.finalizeETHWithdrawal.selector,3 _from,4 _to,5 _amount,6 _data7 );8 } else {9 message = abi.encodeWithSelector(10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,11 l1Token,12 _l2Token,13 _from,14 _to,15 _amount,16 _data17 );18 }1920 // Send message up to L1 bridge21 // slither-disable-next-line reentrancy-events22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);2324 // slither-disable-next-line reentrancy-events25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);26 }2728 /************************************29 * Cross-chain Function: Depositing *30 ************************************/3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function finalizeDeposit(36 address _l1Token,37 address _l2Token,38 address _from,39 address _to,40 uint256 _amount,41 bytes calldata _dataすべて表示コピー
この関数は、L1StandardBridge
が呼び出します。
1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {コピー
メッセージの発信元が適切であることを確認します。 この関数は、_mint
を呼び出すものであり、L1上でブリッジが所有するトークンに含まれないトークンを提供するために使用できるため、重要です。
1 // Check the target token is compliant and2 // verify the deposited token on L1 matches the L2 deposited token representation here3 if (4 // slither-disable-next-line reentrancy-events5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&6 _l1Token == IL2StandardERC20(_l2Token).l1Token()コピー
以下の健全性チェックを実行してください:
- 正しいインターフェースがサポートされていること。
- L2上のERC-20コントラクトにおけるL1アドレスが、L1上のトークンソースと一致すること。
1 ) {2 // When a deposit is finalized, we credit the account on L2 with the same amount of3 // tokens.4 // slither-disable-next-line reentrancy-events5 IL2StandardERC20(_l2Token).mint(_to, _amount);6 // slither-disable-next-line reentrancy-events7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);コピー
健全性チェックに合格したら、入金を確定します。
- トークンをミントします。
- 適切なイベントを発行します。
1 } else {2 // Either the L2 token which is being deposited-into disagrees about the correct address3 // of its L1 token, or does not support the correct interface.4 // This should only happen if there is a malicious L2 token, or if a user somehow5 // specified the wrong L2 token address to deposit into.6 // In either case, we stop the process here and construct a withdrawal7 // message so that users can get their funds out in some cases.8 // There is no way to prevent malicious token contracts altogether, but this does limit9 // user error and mitigate some forms of malicious contract behavior.すべて表示コピー
L2のトークンアドレスが間違っており、検知可能なエラーが発生した場合、入金をキャンセルし、トークンをL1に返却する必要があります。 これをL2から実行する唯一の方法は、不正申し立て期間が経過してからメッセージを送信することですが、それでも、トークンを永遠に失うよりは、ユーザーにとってははるかにベターな選択でしょう。
1 bytes memory message = abi.encodeWithSelector(2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,3 _l1Token,4 _l2Token,5 _to, // switched the _to and _from here to bounce back the deposit to the sender6 _from,7 _amount,8 _data9 );1011 // Send message up to L1 bridge12 // slither-disable-next-line reentrancy-events13 sendCrossDomainMessage(l1TokenBridge, 0, message);14 // slither-disable-next-line reentrancy-events15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);16 }17 }18}すべて表示コピー
まとめ
標準ブリッジは、アセットを移転する上で最も柔軟なメカニズムです。 しかし、汎用性が高いため、必ずしも使いやすいメカニズムではない場合もあります。 特に出金については、大部分のユーザーは、異議申し立て期間が存在せず、出金を最終確認するためにMerkleプルーフを必要としないサードパーティーのブリッジ(opens in a new tab)を使いたいと考えるでしょう。
サードパーティのブリッジは通常、少額の手数料(標準ブリッジの出金にかかるガス代よりも安価な場合が多いです)で、L1上でアセットを保持する機能を提供します。 ブリッジ(または、ブリッジを稼働するスタッフ)がL1上での資産不足を予想する場合、L2から必要な資産が移転されます。 これらは非常に大規模な出金になるため、出金コストは高額を対象として償却され、手数料が全体に占める割合が非常に低くなります。
この記事が、レイヤー2の仕組みと、明確で安全なSolidityコードの書き方について、より理解を深めることに役立つことを願っています。
最終編集者: @lukassim(opens in a new tab), 2024年4月26日